Objectives

  • Quality control and post-processing of a Visium dataset
  • Learn visualization approaching for spatial data
  • Apply descriptive statistics for lattice-based spatial data

This notebook will analyze an anaplastic Wilms Tumor sample from a 3 year old male biopsied at initial diagnosis. The dataset comes from the Single-cell Pediatric Cancer Atlas Portal, project SCPCP000006.

Set up

Load libraries and set random seed:

suppressPackageStartupMessages({
  library(SpatialExperiment) # core object 
  library(VisiumIO) # I/O, but we'll still use :: in the code
  library(ggspavis) # plotting
  library(ggplot2) # plotting
  library(patchwork) # plotting
  library(Voyager) # SFE object & spatial descriptive stats
})
set.seed(2026)

Paths:

data_path <- here::here( 
  "data", 
  "SCPCS000190",
  "SCPCL000429_spatial"
) 

What’s in Space Ranger output? Must note that this was processed with an older version of Space Ranger 1.3.1.

dir(data_path)
[1] "filtered_feature_bc_matrix" "raw_feature_bc_matrix"     
[3] "spatial"                   
filtered_feature_bc_matrix
raw_feature_bc_matrix
spatial
dir(file.path(data_path, "spatial"))
[1] "aligned_fiducials.jpg"     "detected_tissue_image.jpg"
[3] "scalefactors_json.json"    "tissue_hires_image.png"   
[5] "tissue_lowres_image.png"   "tissue_positions_list.csv"
aligned_fiducials.jpg
detected_tissue_image.jpg
scalefactors_json.json
tissue_hires_image.png
tissue_lowres_image.png
tissue_positions_list.csv

Import Visium data

We’ll use the VisiumIO package:

# define handle, essentially
tenx_object_raw_matrix <- VisiumIO::TENxVisium(
  resources = file.path(data_path, "raw_feature_bc_matrix"),
  spatialResource = file.path(data_path, "spatial"),
  # this argument is needed: the default argument includes "cytassist" 
  # but ScPCA samples don't have that one; only have these
  images = c("lowres", "hires", "detected", "aligned")
)

# actually import it
spe <- VisiumIO::import(tenx_object_raw_matrix)

Let’s have a look:

spe
class: SpatialExperiment 
dim: 60664 4992 
metadata(2): resources spatialList
assays(1): counts
rownames(60664): ENSG00000284662 ENSG00000186827 ... ENSG00000277475
  ENSG00000275405
rowData names(3): ID Symbol Type
colnames(4992): AAACAACGAATAGTTC-1 AAACAAGTATCTCCCA-1 ...
  TTGTTTGTATTACACG-1 TTGTTTGTGTAAATTC-1
colData names(4): in_tissue array_row array_col sample_id
reducedDimNames(0):
mainExpName: Gene Expression
altExpNames(0):
spatialCoords names(2) : pxl_col_in_fullres pxl_row_in_fullres
imgData names(4): sample_id image_id data scaleFactor

It’s essentially an SCE, but with a few more bells and whistles.

Note that we have 4992 spots - this is not a random value! It’s the number of spots on Visium’s 6.5 x 6.5 slide.

Unlike SCEs, we have spatial information in a dedicated spatialCoords slot:

# handy function from our code package SpatialExperiment
# literally is giving x/y coordinates
head(spatialCoords(spe))
                   pxl_col_in_fullres pxl_row_in_fullres
AAACAACGAATAGTTC-1               5084               2772
AAACAAGTATCTCCCA-1              14913              12629
AAACAATCTACTAGCA-1               8159               3352
AAACACCAATAACTGC-1               5476              14452
AAACAGAGCGACTCCT-1              13971               5505
AAACAGCTTTCAGAAG-1               4324              11289

We also have a slot that just holds the images, imgData, although looking at it directly isn’t very interesting. But, this stored information will help us make figures!

imgData(spe)
DataFrame with 4 rows and 4 columns
    sample_id    image_id   data scaleFactor
  <character> <character> <list>   <numeric>
1    sample01      lowres   ####   0.0286028
2    sample01       hires   ####   0.0953425
3    sample01    detected   ####          NA
4    sample01     aligned   ####          NA

We should also point out what’s in colData. Unlike an SCE, this isn’t cell metadata. In an SPE, it’s spot metadata; recall that each spot may contain multiple cells, which can even include partial cells!

head(colData(spe))
DataFrame with 6 rows and 4 columns
                   in_tissue array_row array_col   sample_id
                   <integer> <integer> <integer> <character>
AAACAACGAATAGTTC-1         1         0        16    sample01
AAACAAGTATCTCCCA-1         1        50       102    sample01
AAACAATCTACTAGCA-1         0         3        43    sample01
AAACACCAATAACTGC-1         1        59        19    sample01
AAACAGAGCGACTCCT-1         1        14        94    sample01
AAACAGCTTTCAGAAG-1         1        43         9    sample01

Hmmm, check out that in_tissue column! It contains 0/1 because we read in the raw Space Ranger output, which includes all spots including those which don’t actually overlap tissue. Ultimately, we won’t want to analyze the spots that are not on top of tissue, so we’ll have to deal with that.

Having all this image information in the SPE object means we can plot directly. The package ggspavis provides several useful functions for plotting, let’s see the highlights:

# Spots overlaying the H&E, and we can see the slide boundaries as well
ggspavis::plotVisium(spe)

By default this shows the spots, but we can hide them and zoom into the tissue on the slide. This is a helpful way to pop up the H&E for side-by-side comparisons with other plots you might make.

ggspavis::plotVisium(spe, spots = FALSE, zoom = TRUE) 

Let’s take a moment to chat about Wilm’s Tumor - these tumors in the developing kidney are often composed of a couple histologic compartments, blastemal, stromal (with mesenchymal biology), and sometimes epithelial components. In this section, we can see two major components: the bluer indicating densely cellular, ECM-poor regions consistent with blastema, and paler pink regions consistent with stromal tissue.

Let’s actually go ahead and save this plot to a variable; it will be a convenient way for us to quickly pop up the H&E for side-by-side comparison with other plots we’ll make later.

p_he <- ggspavis::plotVisium(spe, spots = FALSE, zoom = TRUE) 

There’s another function called plotCoords which hides the H&E to just show the spots; this plot is not currently very compelling, but once we start coloring by things it will be much more fun!

# no H&E
ggspavis::plotCoords(spe, point_size = 1) 

Quality control

Removing uninformative bins

We only care about spots on top of tissue, so let’s begin by removing the in_tissue = 0 spots. (Note that reading in the filtered_feature_bc_matrix version of Space Ranger output would already be filtered to only 1 in this column, so we’re only taking this step because we read in the raw output).

We can visualize which spots those are, and we’ll do it over the H&E to clearly see the relationship. We’ll use the annotate argument, but plotVisium sees that this column is an integer and forces it to use a continuous color scale; we’ll go ahead and make it a factor for plotting.

# make a factor version of this column to plot with
spe$in_tissue_factor <- as.factor(spe$in_tissue)

ggspavis::plotVisium(
  spe, 
  annotate = "in_tissue_factor", 
  # custom palette so we can see clearly
  pal = c("red", "lightblue")
)

  • The tissue overhang on the left isn’t colored at all - indeed, there aren’t spots at those coordinates outside the slide
  • Red points are those to filter out - they are uninformative. Worth noting that some of these appear “inside” the tissue.
# keep only spots that are in the tissue
spe <- spe[, spe$in_tissue == 1]
spe
class: SpatialExperiment 
dim: 60664 4210 
metadata(2): resources spatialList
assays(1): counts
rownames(60664): ENSG00000284662 ENSG00000186827 ... ENSG00000277475
  ENSG00000275405
rowData names(3): ID Symbol Type
colnames(4210): AAACAACGAATAGTTC-1 AAACAAGTATCTCCCA-1 ...
  TTGTTTGTATTACACG-1 TTGTTTGTGTAAATTC-1
colData names(5): in_tissue array_row array_col sample_id
  in_tissue_factor
reducedDimNames(0):
mainExpName: Gene Expression
altExpNames(0):
spatialCoords names(2) : pxl_col_in_fullres pxl_row_in_fullres
imgData names(4): sample_id image_id data scaleFactor

Now, we’re down to 3701 spots and we can see this reflected in a plot. We now only see spots with tissue underneath. Onwards!

ggspavis::plotVisium(spe)

Filtering for bin quality

We could do the same kind of QC that we do for single-cell data, which (usually) involves defining global thresholds for e.g. number of detected UMIs or percent mito. However, spatial data doesn’t lend itself as well to this type of filtering since global thresholds may not be suitable for the different tissues. Compared to spatial data, single-cell data more closely (but of course not exactly!) resembles a homogeneous pool of cells.

Let’s go ahead and calculate some QC stats and visualize them:

is_mito <- grepl("(^MT-)|(^mt-)", rowData(spe)$Symbol)
mito_ensembl <- rownames(spe)[is_mito]
spe <- scater::addPerCellQC(spe, subsets=list(mito=mito_ensembl))

ggspavis comes with a helpful QC plotter (makes histograms by default but has a couple more options!)

p1 <- ggspavis::plotObsQC(spe, x_metric = "sum") + ggtitle("sum")
p2 <- ggspavis::plotObsQC(spe, x_metric = "detected") + ggtitle("detected")
p3 <- ggspavis::plotObsQC(spe, x_metric = "subsets_mito_percent") + ggtitle("mito %")

p1 + p2 + p3

Distributions don’t look like there are any major issues with quality here. One reason for this is - these are all spots (aggregates of cells), not cells! Data is sparse, but not quite as sparse.

Let’s plot the same stats on the slide:

p1 <- plotCoords(spe, annotate="sum", point_size = 1) + ggtitle("sum")
p2 <- plotCoords(spe, annotate="detected", point_size = 1) + ggtitle("detected")
p3 <- plotCoords(spe, annotate="subsets_mito_percent", point_size = 1) + ggtitle("mito %")
p1 + p2 + p3 + p_he

We do see a relationship here with tissue type and QC stats that suggests we don’t have globally homogeneous patterns -

  • the likely stromal regions look like they have relatively higher mito (although it’s low all around!)
  • the likely blastema regions tend to have more detected genes

This tells us that there is local structure in the data, and using global thresholds could be too aggressive/not aggressive enough depending on the local context.

Local filtering with Spot Sweeper

A more spatially-suited approach comes from SpotSweeper which models QC stats to identify local outliers.

We’ll detect local outliers based on these three QC stats. This function will add some colData columns we we can use to find all the local outliers.

spe <- SpotSweeper::localOutliers(spe, metric="sum", direction="lower", log=TRUE) # lower-value outliers should be detected
spe <- SpotSweeper::localOutliers(spe, metric="detected", direction="lower", log=TRUE) # lower-value outliers should be detected
spe <- SpotSweeper::localOutliers(spe, metric="subsets_mito_percent", direction="higher", log=FALSE) # higher-value outliers should be detected

How many of each kind of outlier?

c("sum_outliers", "detected_outliers", "subsets_mito_percent_outliers") |>
  purrr::set_names() |>
  purrr::map(\(x) table(spe[[x]]))
$sum_outliers

FALSE  TRUE 
 4175    35 

$detected_outliers

FALSE  TRUE 
 4169    41 

$subsets_mito_percent_outliers

FALSE  TRUE 
 4190    20 

We’ll use a built-in plotting function from SpotSweeper to plot these results (the ggspavis plotting isn’t quite as nice in this case). Each plot highlights the spots to remove, and spots are colored based on a different stat. Again we’ll include the H&E as a panel for ease of comparison.

p1 <- SpotSweeper::plotQCmetrics(spe, metric = "sum_log", outliers = "sum_outliers") + ggtitle("log sum")
p2 <- SpotSweeper::plotQCmetrics(spe, metric = "detected_log", outliers = "detected_outliers") + ggtitle("log detected")
p3 <- SpotSweeper::plotQCmetrics(spe, metric = "subsets_mito_percent", outliers = "subsets_mito_percent_outliers") + ggtitle("mito %")

p1 + p2 + p3 + p_he

Some of these spots are being flagged by multiple stats, in particular both sum and detected. Let’s go ahead and filter all these cells out:

# combine all outliers into "local_outliers" column to filter on
spe$local_outliers <- as.logical(spe$sum_outliers) |
    as.logical(spe$detected_outliers) |
    as.logical(spe$subsets_mito_percent_outliers)

spe <- spe[, spe$local_outliers == FALSE]

Normalization

With single-cell, a popular normalization approach we adopt involves deconvolution - we do a quick clustering so we can get groups of cells that will share a size factor, for faster processing. But this isn’t suitable for spot-based spatial data since our units aren’t cells, but potentially heterogeneous groups of cells (whole or partial).

We’ll therefore use going to use library-size normalization here to correct for sequencing depth but preserve the other heterogeneous biological signal. Bear in mind the caveat that in some cases library size itself is [also confounded with spatial structure]((https://link.springer.com/article/10.1186/s13059-024-03241-7); so, keep your eyes peeled for normalization developments, since this space is evolving rapidly!

spe <- scuttle::computeLibraryFactors(spe)
spe <- scuttle::logNormCounts(spe)
spe # now we have a logcounts assay
class: SpatialExperiment 
dim: 60664 4149 
metadata(2): resources spatialList
assays(2): counts logcounts
rownames(60664): ENSG00000284662 ENSG00000186827 ... ENSG00000277475
  ENSG00000275405
rowData names(4): ID Symbol Type subsets_mito
colnames(4149): AAACAACGAATAGTTC-1 AAACAAGTATCTCCCA-1 ...
  TTGTTTGTATTACACG-1 TTGTTTGTGTAAATTC-1
colData names(21): in_tissue array_row ... local_outliers sizeFactor
reducedDimNames(0):
mainExpName: Gene Expression
altExpNames(0):
spatialCoords names(2) : pxl_col_in_fullres pxl_row_in_fullres
imgData names(4): sample_id image_id data scaleFactor

Dimension reduction

For this step, we’re going to get a reduced dimension representation using just the lens of gene expression, “ignoring” spatial information. (Later, we’ll see complementary approaches to some of this which leverages spatial information).

This all tells us we have to be careful when interpreting analyses on spatial data: “to what extent was spatial information taken into account” is something you’ll want to understand.

Let’s do HVG selection (again, this does not consider spatial patterns! we’ll learn about spatially variable genes later), PCA, UMAP, and clustering (once again, this won’t be spatial regions! just spot similarity based on expression - that’s not the same as cells, either!) So, we’ll use a SCE-style approach:

num_genes <- 2000
gene_variance <- scran::modelGeneVar(spe)
hv_genes <- scran::getTopHVGs(gene_variance, n = num_genes)

spe <- scater::runPCA(spe, subset_row = hv_genes)
spe <- scater::runUMAP(spe, dimred = "PCA")

nn_clusters <- scran::clusterCells(
  spe, # object to perform clustering on
  use.dimred = "PCA", # perform clustering on the PCA matrix
  BLUSPARAM = bluster::NNGraphParam(
    k = 20,
    type = "jaccard",
    cluster.fun = "louvain"
  )
)
spe$nn_cluster <- nn_clusters

We’ll plot the UMAP colored by clusters, because we love UMAPs:

scater::plotUMAP(spe, color_by = "nn_cluster") +
  # use distinct palette so we can match up with a spatial view next
  ggokabeito::scale_color_okabe_ito()
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

How do these clusters come out when using the spatial layout? We have coordinates, after all - let’s use them!

p1 <- ggspavis::plotCoords(spe, annotate = "nn_cluster", point_size = 1) +
  # matching palette to UMAP
  ggokabeito::scale_color_okabe_ito()
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.
p1 + p_he

A lot of the clusters are intermingled in space, showing that this analysis is really not spatially-aware! But, we do see some interesting patterns - check out clusters 1, 5, & 7 in particular - they look stroma-y; also, cluster 8 ooks like a defined region in the H&E, but I’m not a pathologist so let’s not overinterpret here. When expression and tissue structure are correlated, expression-only clusters can pick up on certain spatial regions, but this is not the same as finding “true” spatial domains.

Exploring spot-based spatial data

Let’s explore this data a little more, using some marker genes. We don’t have specific cells as units here, but we can use marker gene expression to build some descriptive intuition for the data.

We’ve defined some marker genes here for visualization that correspond to expected Wilm’s Tumor biology:

marker_genes <- c(
  "WT1 (kidney development, maglinant in WT)" = "ENSG00000184937",
  "SIX1 (blastema, malignant)" = "ENSG00000126778",
  "MYCN (malignant)" = "ENSG00000134323",
  "COL1A1 (fibroblast/mesenchymal)" = "ENSG00000108821",
  "TAGLN (myofibroblast/mesenchymal)" = "ENSG00000149591"
)

Let’s plot the expression of these genes, using both UMAP and the spatial layouts to compare:

cluster_umap <- scater::plotUMAP(spe, color_by = "nn_cluster", point_size = 0.5, point_alpha = 0.5) +
  ggtitle("expression-based clusters") + 
  coord_equal() +
  ggokabeito::scale_color_okabe_ito() 
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.
marker_umaps <- marker_genes |>
  purrr::imap(
    \(ensembl, gene_name) {
      scater::plotUMAP(spe, color_by = ensembl, point_size = 0.5, point_alpha = 0.5) + ggtitle(gene_name) + coord_equal()
    }
  ) |>
  # cluster umap and then the marker gene umaps
  append(values = cluster_umap, after = 0)

patchwork::wrap_plots(marker_umaps, nrow = 2)

cluster_coords <- ggspavis::plotCoords(spe, annotate = "nn_cluster", point_size = 0.75) +
  ggtitle("expression-based clusters") + 
  ggokabeito::scale_color_okabe_ito() 
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.
marker_coords <- marker_genes |>
  purrr::imap(
    \(ensembl, gene_name) {
      # default assay is counts, override!
      ggspavis::plotCoords(spe, annotate = ensembl, point_size = 0.75, assay = "logcounts") +
        scale_color_distiller(palette = "Blues", direction = 1) + 
        ggtitle(gene_name)
    }
  ) |>
  # cluster cooords and then the marker gene coords
  append(values = cluster_coords, after = 0) 
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.
p_he + patchwork::wrap_plots(marker_coords, nrow = 2) + plot_layout(widths = c(1,2))

Based on these observations, it seems like there could be some spatial patterns/variability here; interpret. We’ll talk more rigorously about quantifying spatially-variable genes later in the workshop, but for now, we can take this opportunity to to ask some descriptive questions about these genes’ expression across space.

Spatial descriptive statistics

Although spatial analysis is an emerging field in transcriptomics, it’s not new to science! Folks in geography, epidemiology, ecology, and more have been thinking about spatial data for a long time, so we can draw from existing quantitative approaches.

Spot-based spatial data is analogous to “lattice-structure” spatial data, in contrast to high-res spatial technologies that are “point-pattern” data; see the pasta paper.

The R package spdep already exists to work with lattice-structure spatial data, and helpfully folks have already started to put this (and other spatial platforms) to work for transcriptomics data. The Voyager/SpatialFeatureExperiment: https://pachterlab.github.io/voyager/index.html framework is complementary to SpatialExperiment and can help us use these tools. The SFE object is an expanded SPE object with more slots to facilitate spatial analysis with existing tools (we won’t get into these weeds much though, suffice to say it’s a super rich environment).

When working with spot data, we can turn it into a graph and ask descriptive (not modeling!) questions about spatial structure directly. Let’s go!

First, we’ll need to get it into the SFE framework and convert the SPE to an SFE object.

# TODO: I have to remove the image before converting, errors otherwise. Can this be avoided?
# Error: [ext] invalid extent
spe_noimg <- spe
imgData(spe_noimg) <- imgData(spe_noimg)[0, , drop=FALSE]
sfe <- toSpatialFeatureExperiment(spe_noimg)
#sfe <- mirror(sfe, direction = "vertical") # use mirror so plots between ggspavis and this one match. 
# OR, let it be a mirror to serve as a teachable moment about space!

One way to make a graph is using k-nearest neighbors, but there’s other ways (all of these choices matter to some degree, and we should discuss that!): Now we’ll make a graph using:

  • spot centroids as nodes (don’t skip that this is a choice!)
  • knn approach with k = 10, as a starting point
  • no weighting
colGraph(sfe, "knn10") <- SpatialFeatureExperiment::findSpatialNeighbors(
  sfe, 
  type = "centroids", 
  method = "knearneigh", 
  k = 10
)

This will get stored in colGraph slot in the SFE object.

We can visualize it - check out those edges and nodes, it’s a graph!

Voyager::plotColGraph(
  sfe, 
  colGraphName = "knn10", 
  colGeometryName = "centroids", 
  segment_size = 0.1,
  geometry_size = 0.3
) +
  theme_void() # we just want the graph

Hey notice anything interesting about this image? It’s a mirror of how ggspavis plotted it! Is that a problem? Well, no - in space we can mirror and rotate and such, but the spatial relationships are preserved. This is a big hint that specific coordinates may not always be the same, but the distances should be.

It seems potentially cute to zoom in to prove to you that k = 10:

Voyager::plotColGraph(
  sfe, 
  colGraphName = "knn10", 
  colGeometryName = "centroids", 
  segment_size = 0.1,
  geometry_size = 5
) +
  scale_y_continuous(limits = c(11500, 12000)) + 
  scale_x_continuous(limits = c(4000,4300)) +
  theme_void()

Alright, now that we have a graph, let’s see an example of using it for exploratory analysis. A common descriptive statistic used for lattice spatial data is “Moran’s I” which quantifies spatial autocorrelation. It asks, is the spatial distribution of a variable of interest dispersed, random, or clustered? We’ll use it to look at gene expression of some of our marker genes of interest. In this case, Moran’s I tells us: do neighboring spots (hint: this will depend on your definition of a neighborhood!_) have very similar, very dissimilar, or totally unrelated gene expression, where:

  • close to 1 values: Nearby spots tend to have similar expression
  • ~0: looks random in space
  • close to -1 values: Nearby spots tend to have different expression (less commonly of interest in this sort of biology)

We’ll calculate this with Voyager on our SFE object using the graph we just built.

sfe <- Voyager::runUnivariate(
  sfe, 
  type = "moran", 
  features = marker_genes, 
  colGraphName = "knn10"
)

This stores results in rowData:

rowData(sfe)[marker_genes, ]
DataFrame with 5 rows and 6 columns
                             ID      Symbol            Type subsets_mito
                    <character> <character>     <character>    <logical>
ENSG00000184937 ENSG00000184937         WT1 Gene Expression        FALSE
ENSG00000126778 ENSG00000126778        SIX1 Gene Expression        FALSE
ENSG00000134323 ENSG00000134323        MYCN Gene Expression        FALSE
ENSG00000108821 ENSG00000108821      COL1A1 Gene Expression        FALSE
ENSG00000149591 ENSG00000149591       TAGLN Gene Expression        FALSE
                moran_sample01 K_sample01
                     <numeric>  <numeric>
ENSG00000184937       0.400004    5.36664
ENSG00000126778       0.146762    2.52327
ENSG00000134323       0.116602    2.06943
ENSG00000108821       0.598150    2.90257
ENSG00000149591       0.768734    4.80837

This makes some sense given what we when plotting gene expression:

  • SIX1 and MYCN were just splotched all around randomly in much of the slide
  • COL1A1 and TAGLN were more concentrated in certain areas
  • WT1 was somewhere in the middle - generally expressed by less variation from spot-to-spot in the plots earlier, per vibes

This global statistic gave us an overall picture, but we can calculate a variant of this statistic also called Local Moran’s I which is, you guessed it, a local version. Rather than giving a single value for the dataset, we’ll get a per-spot value (or rather, per node in the graph) that tells us “how much does this spot agree with its immediate neighborhood”?

  • high: spot is similar to neighbors and all are high OR all are low
  • ~0: spatially neutral, no real relationship to neighbors
  • low: spot is different from neighbors where high surrounded by low or vice versa

Let’s go:

sfe <- Voyager::runUnivariate(
  sfe, 
  type = "localmoran", 
  features = marker_genes, 
  colGraphName = "knn10", 
  name = "localmoran_knn10" # save it as this name
)

# stores it here, not so pretty...
localResults(sfe)$localmoran_knn10
DataFrame with 4149 rows and 5 columns
                                             ENSG00000184937
                                                <data.frame>
AAACAACGAATAGTTC-1  1.667219796:-3.16454e-04:1.30970e-01:...
AAACAAGTATCTCCCA-1  0.007633311:-1.74338e-07:7.21757e-05:...
AAACACCAATAACTGC-1 -0.000607848:-1.18586e-09:4.90946e-07:...
AAACAGAGCGACTCCT-1  0.051163178:-2.43975e-04:1.00981e-01:...
AAACAGCTTTCAGAAG-1  0.078470169:-3.93378e-05:1.62852e-02:...
...                                                      ...
TTGTTTCACATCCAGG-1    -0.1995113:-5.79632e-04:0.23982823:...
TTGTTTCATTAGTCTA-1    -0.0648147:-8.20217e-05:0.03395418:...
TTGTTTCCATACAACT-1     0.0496851:-5.42162e-05:0.02244428:...
TTGTTTGTATTACACG-1     0.0765047:-1.90444e-05:0.00788423:...
TTGTTTGTGTAAATTC-1     2.1740629:-3.44664e-04:0.14264156:...
                                            ENSG00000126778
                                               <data.frame>
AAACAACGAATAGTTC-1 -2.15283539:-3.16619e-04:0.131038522:...
AAACAAGTATCTCCCA-1  0.12587206:-9.83964e-05:0.040732072:...
AAACACCAATAACTGC-1 -0.00824221:-7.23225e-07:0.000299415:...
AAACAGAGCGACTCCT-1 -0.52228726:-3.17655e-04:0.131467282:...
AAACAGCTTTCAGAAG-1 -0.23168190:-1.34973e-04:0.055871160:...
...                                                     ...
TTGTTTCACATCCAGG-1   -0.0939208:-1.35766e-05:0.00562064:...
TTGTTTCATTAGTCTA-1   -0.0682406:-5.91852e-04:0.24488153:...
TTGTTTCCATACAACT-1    0.0451762:-6.60282e-05:0.02733382:...
TTGTTTGTATTACACG-1    0.1783573:-7.46413e-05:0.03089916:...
TTGTTTGTGTAAATTC-1   -0.0634922:-6.58503e-06:0.00272618:...
                                          ENSG00000134323
                                             <data.frame>
AAACAACGAATAGTTC-1 -0.8411838:-1.16198e-04:0.04810041:...
AAACAAGTATCTCCCA-1 -0.0491596:-5.23386e-04:0.21656837:...
AAACACCAATAACTGC-1 -0.0717147:-4.11319e-05:0.01702787:...
AAACAGAGCGACTCCT-1 -0.0755661:-1.11880e-05:0.00463178:...
AAACAGCTTTCAGAAG-1 -0.0174028:-2.23178e-05:0.00923935:...
...                                                   ...
TTGTTTCACATCCAGG-1 0.0111875:-1.11170e-06:0.000460242:...
TTGTTTCATTAGTCTA-1 0.2150468:-7.14312e-05:0.029570378:...
TTGTTTCCATACAACT-1 0.2422880:-2.46435e-04:0.101998874:...
TTGTTTGTATTACACG-1 0.0572942:-1.06836e-04:0.044225137:...
TTGTTTGTGTAAATTC-1 0.3536584:-5.23386e-04:0.216568367:...
                                             ENSG00000108821
                                                <data.frame>
AAACAACGAATAGTTC-1      3.097516:-1.00038e-03:0.41374280:...
AAACAAGTATCTCCCA-1     -0.049879:-9.91574e-06:0.00410507:...
AAACACCAATAACTGC-1      0.159551:-1.96079e-05:0.00811751:...
AAACAGAGCGACTCCT-1     -0.140422:-3.45277e-05:0.01429394:...
AAACAGCTTTCAGAAG-1      0.321191:-9.37702e-05:0.03881716:...
...                                                      ...
TTGTTTCACATCCAGG-1 -0.159006137:-2.35465e-05:9.74803e-03:...
TTGTTTCATTAGTCTA-1  0.104516002:-7.92831e-05:3.28206e-02:...
TTGTTTCCATACAACT-1  0.000407591:-2.20039e-09:9.10962e-07:...
TTGTTTGTATTACACG-1  1.949902019:-1.46119e-03:6.04047e-01:...
TTGTTTGTGTAAATTC-1  0.848563795:-2.20292e-04:9.11805e-02:...
                                           ENSG00000149591
                                              <data.frame>
AAACAACGAATAGTTC-1  5.2638003:-1.08466e-03:0.448560970:...
AAACAAGTATCTCCCA-1 -0.0363059:-1.48510e-06:0.000614829:...
AAACACCAATAACTGC-1  0.2883653:-1.40370e-04:0.058104774:...
AAACAGAGCGACTCCT-1 -0.0448511:-1.64269e-05:0.006800604:...
AAACAGCTTTCAGAAG-1  0.4025505:-1.40370e-04:0.058104774:...
...                                                    ...
TTGTTTCACATCCAGG-1   0.3590522:-1.40370e-04:0.05810477:...
TTGTTTCATTAGTCTA-1   0.0369287:-4.97746e-06:0.00206066:...
TTGTTTCCATACAACT-1  -0.0548757:-8.82533e-06:0.00365365:...
TTGTTTGTATTACACG-1   0.1463867:-1.31907e-04:0.05460239:...
TTGTTTGTGTAAATTC-1   2.6481197:-8.84656e-04:0.36592308:...
# we can plot with voyager, since it's in a special slot
# or we'd have to pull it out to store back in our SPE
Voyager::plotLocalResult(
  sfe,
  name = "localmoran_knn10",
  features = marker_genes,
  colGeometryName = "centroids",
  divergent = TRUE, # palette
  diverge_center = 0 # palette 
  ) 

For easier comparison, we can also use the standardized (z-score) values by specifying the specific attribute (the default was "Ii", the actual stat).

Voyager::plotLocalResult(
  sfe,
  name = "localmoran_knn10",
  features = marker_genes,
  attribute = "Z.Ii", # plot the normalized moran's i
  colGeometryName = "centroids",
  divergent = TRUE, 
  diverge_center = 0 
  ) 

Scale matters!

We did a graph with 10 nearest neighbors. The number of neighbors is going to matter here!

Pick one gene (provide the ensembl id) and see how interpretation differs at different k’s. Try out a really high (k>=50) and really low number (k<=3) below to calculate and plot Local Moran’s I again - the real value, not scaled. What do you see?

gene <- marker_genes[1] # WT1, for example
colGraph(sfe, "knn100") <- findSpatialNeighbors(sfe, type="centroids", method = "knearneigh", k=100)
colGraph(sfe, "knn2") <- findSpatialNeighbors(sfe, type="centroids", method = "knearneigh", k=2)

sfe <- Voyager::runUnivariate(
  sfe, 
  type="localmoran", 
  features = gene, 
  colGraphName="knn100", 
  name = "localmoran_knn100" 
)
sfe <- Voyager::runUnivariate(
  sfe, 
  type="localmoran", 
  features = gene, 
  colGraphName="knn2", 
  name = "localmoran_knn2" 
)

p1 <- Voyager::plotLocalResult(
  sfe,
  name = "localmoran_knn100",
  features = gene,
  colGeometryName = "centroids",
  divergent = TRUE,
  diverge_center = 0
) 


p2 <- Voyager::plotLocalResult(
  sfe,
  name = "localmoran_knn2",
  features = gene,
  colGeometryName = "centroids",
  divergent = TRUE,
  diverge_center = 0
) 

p1 + p2

When k is very high, there’s a lot of neighbors being consider. Expression will have to be similar cross them all to get a high value for this stat. But, when k is very low, there’s very few neighbors! Much finer-grained picture of similarity between spots.

Session Info

sessionInfo()
R version 4.5.2 (2025-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Sequoia 15.5

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats4    stats     graphics  grDevices datasets  utils     methods  
[8] base     

other attached packages:
 [1] Voyager_1.12.0                  SpatialFeatureExperiment_1.12.1
 [3] patchwork_1.3.2                 ggspavis_1.16.0                
 [5] ggplot2_4.0.1                   VisiumIO_1.6.3                 
 [7] TENxIO_1.12.1                   SpatialExperiment_1.20.0       
 [9] SingleCellExperiment_1.32.0     SummarizedExperiment_1.40.0    
[11] Biobase_2.70.0                  GenomicRanges_1.62.1           
[13] Seqinfo_1.0.0                   IRanges_2.44.0                 
[15] S4Vectors_0.48.0                BiocGenerics_0.56.0            
[17] generics_0.1.4                  MatrixGenerics_1.22.0          
[19] matrixStats_1.5.0              

loaded via a namespace (and not attached):
  [1] RcppAnnoy_0.0.23          splines_4.5.2            
  [3] BiocIO_1.20.0             bitops_1.0-9             
  [5] tibble_3.3.1              R.oo_1.27.1              
  [7] lifecycle_1.0.5           sf_1.0-24                
  [9] edgeR_4.8.2               rprojroot_2.1.1          
 [11] lattice_0.22-7            MASS_7.3-65              
 [13] magrittr_2.0.4            limma_3.66.0             
 [15] sass_0.4.10               rmarkdown_2.30           
 [17] jquerylib_0.1.4           yaml_2.3.12              
 [19] metapod_1.18.0            otel_0.2.0               
 [21] ggside_0.4.1              sp_2.2-0                 
 [23] DBI_1.2.3                 RColorBrewer_1.1-3       
 [25] multcomp_1.4-29           abind_1.4-8              
 [27] spatialreg_1.4-2          purrr_1.2.1              
 [29] R.utils_2.13.0            RCurl_1.98-1.17          
 [31] TH.data_1.1-5             sandwich_3.1-1           
 [33] ggrepel_0.9.6             irlba_2.3.5.1            
 [35] terra_1.8-93              units_1.0-0              
 [37] RSpectra_0.16-2           dqrng_0.4.1              
 [39] DelayedMatrixStats_1.32.0 codetools_0.2-20         
 [41] DropletUtils_1.30.0       DelayedArray_0.36.0      
 [43] scuttle_1.20.0            tidyselect_1.2.1         
 [45] memuse_4.2-3              farver_2.1.2             
 [47] ScaledMatrix_1.18.0       viridis_0.6.5            
 [49] jsonlite_2.0.0            escheR_1.10.0            
 [51] BiocNeighbors_2.4.0       e1071_1.7-17             
 [53] survival_3.8-3            scater_1.38.0            
 [55] tools_4.5.2               ggnewscale_0.5.2         
 [57] Rcpp_1.1.1                glue_1.8.0               
 [59] gridExtra_2.3             SparseArray_1.10.8       
 [61] BiocBaseUtils_1.12.0      xfun_0.56                
 [63] here_1.0.2                EBImage_4.52.0           
 [65] dplyr_1.1.4               HDF5Array_1.38.0         
 [67] withr_3.0.2               BiocManager_1.30.27      
 [69] fastmap_1.2.0             boot_1.3-32              
 [71] rhdf5filters_1.22.0       bluster_1.20.0           
 [73] spData_2.3.4              digest_0.6.39            
 [75] rsvd_1.0.5                R6_2.6.1                 
 [77] wk_0.9.5                  LearnBayes_2.15.1        
 [79] jpeg_0.1-11               R.methodsS3_1.8.2        
 [81] h5mread_1.2.1             renv_1.1.6               
 [83] data.table_1.18.0         class_7.3-23             
 [85] htmlwidgets_1.6.4         S4Arrays_1.10.1          
 [87] spdep_1.4-1               uwot_0.2.4               
 [89] pkgconfig_2.0.3           scico_1.5.0              
 [91] gtable_0.3.6              S7_0.2.1                 
 [93] XVector_0.50.0            htmltools_0.5.9          
 [95] fftwtools_0.9-11          scales_1.4.0             
 [97] png_0.1-8                 scran_1.38.0             
 [99] SpotSweeper_1.6.0         knitr_1.51               
[101] tzdb_0.5.0                rjson_0.2.23             
[103] coda_0.19-4.1             nlme_3.1-168             
[105] proxy_0.4-29              cachem_1.1.0             
[107] zoo_1.8-15                rhdf5_2.54.1             
[109] KernSmooth_2.23-26        parallel_4.5.2           
[111] vipor_0.4.7               s2_1.1.9                 
[113] pillar_1.11.1             grid_4.5.2               
[115] vctrs_0.7.0               BiocSingular_1.26.1      
[117] beachmat_2.26.0           sfheaders_0.4.5          
[119] cluster_2.1.8.1           beeswarm_0.4.0           
[121] evaluate_1.0.5            readr_2.1.6              
[123] zeallot_0.2.0             magick_2.9.0             
[125] mvtnorm_1.3-3             cli_3.6.5                
[127] locfit_1.5-9.12           compiler_4.5.2           
[129] rlang_1.1.7               labeling_0.4.3           
[131] classInt_0.4-11           spatialEco_2.0-3         
[133] ggbeeswarm_0.7.3          viridisLite_0.4.2        
[135] deldir_2.0-4              BiocParallel_1.44.0      
[137] tiff_0.1-12               Matrix_1.7-4             
[139] hms_1.1.4                 sparseMatrixStats_1.22.0 
[141] Rhdf5lib_1.32.0           statmod_1.5.1            
[143] igraph_2.2.1              bslib_0.9.0              
[145] ggokabeito_0.1.0         
LS0tCnRpdGxlOiAiUmVhZGluZywgcHJvY2Vzc2luZywgYW5kIGV4cGxvcmluZyBWaXNpdW0gZGF0YSIKYXV0aG9yOiBEYXRhIExhYiBmb3IgQUxTRgpkYXRlOiAyMDI2Cm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgojIyBPYmplY3RpdmVzCgotIFF1YWxpdHkgY29udHJvbCBhbmQgcG9zdC1wcm9jZXNzaW5nIG9mIGEgVmlzaXVtIGRhdGFzZXQKLSBMZWFybiB2aXN1YWxpemF0aW9uIGFwcHJvYWNoaW5nIGZvciBzcGF0aWFsIGRhdGEgCi0gQXBwbHkgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyBmb3IgbGF0dGljZS1iYXNlZCBzcGF0aWFsIGRhdGEKCgotLS0KCgpUaGlzIG5vdGVib29rIHdpbGwgYW5hbHl6ZSBhbiBhbmFwbGFzdGljIFdpbG1zIFR1bW9yIHNhbXBsZSBmcm9tIGEgMyB5ZWFyIG9sZCBtYWxlIGJpb3BzaWVkIGF0IGluaXRpYWwgZGlhZ25vc2lzLgpUaGUgZGF0YXNldCBjb21lcyBmcm9tIHRoZSBTaW5nbGUtY2VsbCBQZWRpYXRyaWMgQ2FuY2VyIEF0bGFzIFBvcnRhbCwgW3Byb2plY3QgYFNDUENQMDAwMDA2YF0oaHR0cHM6Ly9zY3BjYS5hbGV4c2xlbW9uYWRlLm9yZy9wcm9qZWN0cy9TQ1BDUDAwMDAwNikuCgojIyBTZXQgdXAKCkxvYWQgbGlicmFyaWVzIGFuZCBzZXQgcmFuZG9tIHNlZWQ6CgoKYGBge3J9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShTcGF0aWFsRXhwZXJpbWVudCkgIyBjb3JlIG9iamVjdCAKICBsaWJyYXJ5KFZpc2l1bUlPKSAjIEkvTywgYnV0IHdlJ2xsIHN0aWxsIHVzZSA6OiBpbiB0aGUgY29kZQogIGxpYnJhcnkoZ2dzcGF2aXMpICMgcGxvdHRpbmcKICBsaWJyYXJ5KGdncGxvdDIpICMgcGxvdHRpbmcKICBsaWJyYXJ5KHBhdGNod29yaykgIyBwbG90dGluZwogIGxpYnJhcnkoVm95YWdlcikgIyBTRkUgb2JqZWN0ICYgc3BhdGlhbCBkZXNjcmlwdGl2ZSBzdGF0cwp9KQpzZXQuc2VlZCgyMDI2KQpgYGAKClBhdGhzOgoKYGBge3J9CmRhdGFfcGF0aCA8LSBoZXJlOjpoZXJlKCAKICAiZGF0YSIsIAogICJTQ1BDUzAwMDE5MCIsCiAgIlNDUENMMDAwNDI5X3NwYXRpYWwiCikgCmBgYAoKV2hhdCdzIGluIFNwYWNlIFJhbmdlciBvdXRwdXQ/Ck11c3Qgbm90ZSB0aGF0IHRoaXMgd2FzIHByb2Nlc3NlZCB3aXRoIGFuIG9sZGVyIHZlcnNpb24gb2YgU3BhY2UgUmFuZ2VyIGAxLjMuMWAuCgpgYGB7cn0KZGlyKGRhdGFfcGF0aCkKZGlyKGZpbGUucGF0aChkYXRhX3BhdGgsICJzcGF0aWFsIikpCmBgYAoKIyMgSW1wb3J0IFZpc2l1bSBkYXRhCgpXZSdsbCB1c2UgdGhlIGBWaXNpdW1JT2AgcGFja2FnZToKCmBgYHtyfQojIGRlZmluZSBoYW5kbGUsIGVzc2VudGlhbGx5CnRlbnhfb2JqZWN0X3Jhd19tYXRyaXggPC0gVmlzaXVtSU86OlRFTnhWaXNpdW0oCiAgcmVzb3VyY2VzID0gZmlsZS5wYXRoKGRhdGFfcGF0aCwgInJhd19mZWF0dXJlX2JjX21hdHJpeCIpLAogIHNwYXRpYWxSZXNvdXJjZSA9IGZpbGUucGF0aChkYXRhX3BhdGgsICJzcGF0aWFsIiksCiAgIyB0aGlzIGFyZ3VtZW50IGlzIG5lZWRlZDogdGhlIGRlZmF1bHQgYXJndW1lbnQgaW5jbHVkZXMgImN5dGFzc2lzdCIgCiAgIyBidXQgU2NQQ0Egc2FtcGxlcyBkb24ndCBoYXZlIHRoYXQgb25lOyBvbmx5IGhhdmUgdGhlc2UKICBpbWFnZXMgPSBjKCJsb3dyZXMiLCAiaGlyZXMiLCAiZGV0ZWN0ZWQiLCAiYWxpZ25lZCIpCikKCiMgYWN0dWFsbHkgaW1wb3J0IGl0CnNwZSA8LSBWaXNpdW1JTzo6aW1wb3J0KHRlbnhfb2JqZWN0X3Jhd19tYXRyaXgpCmBgYAoKCkxldCdzIGhhdmUgYSBsb29rOgoKYGBge3J9CnNwZQpgYGAKCkl0J3MgZXNzZW50aWFsbHkgYW4gYFNDRWAsIGJ1dCB3aXRoIGEgZmV3IG1vcmUgYmVsbHMgYW5kIHdoaXN0bGVzLgoKTm90ZSB0aGF0IHdlIGhhdmUgNDk5MiBzcG90cyAtIHRoaXMgaXMgbm90IGEgcmFuZG9tIHZhbHVlISAKSXQncyB0aGUgbnVtYmVyIG9mIHNwb3RzIG9uIFZpc2l1bSdzIDYuNSB4IDYuNSBzbGlkZS4gCgpVbmxpa2UgYFNDRWBzLCB3ZSBoYXZlIHNwYXRpYWwgaW5mb3JtYXRpb24gaW4gYSBkZWRpY2F0ZWQgYHNwYXRpYWxDb29yZHNgIHNsb3Q6CgpgYGB7cn0KIyBoYW5keSBmdW5jdGlvbiBmcm9tIG91ciBjb2RlIHBhY2thZ2UgU3BhdGlhbEV4cGVyaW1lbnQKIyBsaXRlcmFsbHkgaXMgZ2l2aW5nIHgveSBjb29yZGluYXRlcwpoZWFkKHNwYXRpYWxDb29yZHMoc3BlKSkKYGBgCgpXZSBhbHNvIGhhdmUgYSBzbG90IHRoYXQganVzdCBob2xkcyB0aGUgaW1hZ2VzLCBgaW1nRGF0YWAsIGFsdGhvdWdoIGxvb2tpbmcgYXQgaXQgZGlyZWN0bHkgaXNuJ3QgdmVyeSBpbnRlcmVzdGluZy4KQnV0LCB0aGlzIHN0b3JlZCBpbmZvcm1hdGlvbiB3aWxsIGhlbHAgdXMgbWFrZSBmaWd1cmVzIQoKYGBge3J9CmltZ0RhdGEoc3BlKQpgYGAKCldlIHNob3VsZCBhbHNvIHBvaW50IG91dCB3aGF0J3MgaW4gYGNvbERhdGFgLgpVbmxpa2UgYW4gYFNDRWAsIHRoaXMgaXNuJ3QgY2VsbCBtZXRhZGF0YS4gCkluIGFuIGBTUEVgLCBpdCdzIF9zcG90XyBtZXRhZGF0YTsgcmVjYWxsIHRoYXQgZWFjaCBzcG90IG1heSBjb250YWluIG11bHRpcGxlIGNlbGxzLCB3aGljaCBjYW4gZXZlbiBpbmNsdWRlIHBhcnRpYWwgY2VsbHMhCgpgYGB7cn0KaGVhZChjb2xEYXRhKHNwZSkpCmBgYAoKSG1tbSwgY2hlY2sgb3V0IHRoYXQgYGluX3Rpc3N1ZWAgY29sdW1uIQpJdCBjb250YWlucyAwLzEgYmVjYXVzZSB3ZSByZWFkIGluIHRoZSBgcmF3YCBTcGFjZSBSYW5nZXIgb3V0cHV0LCB3aGljaCBpbmNsdWRlcyBhbGwgc3BvdHMgaW5jbHVkaW5nIHRob3NlIHdoaWNoIGRvbid0IGFjdHVhbGx5IG92ZXJsYXAgdGlzc3VlLgpVbHRpbWF0ZWx5LCB3ZSB3b24ndCB3YW50IHRvIGFuYWx5emUgdGhlIHNwb3RzIHRoYXQgYXJlIG5vdCBvbiB0b3Agb2YgdGlzc3VlLCBzbyB3ZSdsbCBoYXZlIHRvIGRlYWwgd2l0aCB0aGF0LgoKSGF2aW5nIGFsbCB0aGlzIGltYWdlIGluZm9ybWF0aW9uIGluIHRoZSBgU1BFYCBvYmplY3QgbWVhbnMgd2UgY2FuIHBsb3QgZGlyZWN0bHkuClRoZSBwYWNrYWdlIGBnZ3NwYXZpc2AgcHJvdmlkZXMgc2V2ZXJhbCB1c2VmdWwgZnVuY3Rpb25zIGZvciBwbG90dGluZywgbGV0J3Mgc2VlIHRoZSBoaWdobGlnaHRzOgoKYGBge3IsIGZpZy53aWR0aCA9IDV9CiMgU3BvdHMgb3ZlcmxheWluZyB0aGUgSCZFLCBhbmQgd2UgY2FuIHNlZSB0aGUgc2xpZGUgYm91bmRhcmllcyBhcyB3ZWxsCmdnc3BhdmlzOjpwbG90VmlzaXVtKHNwZSkKYGBgCgpCeSBkZWZhdWx0IHRoaXMgc2hvd3MgdGhlIHNwb3RzLCBidXQgd2UgY2FuIGhpZGUgdGhlbSBhbmQgem9vbSBpbnRvIHRoZSB0aXNzdWUgb24gdGhlIHNsaWRlLgpUaGlzIGlzIGEgaGVscGZ1bCB3YXkgdG8gcG9wIHVwIHRoZSBIJkUgZm9yIHNpZGUtYnktc2lkZSBjb21wYXJpc29ucyB3aXRoIG90aGVyIHBsb3RzIHlvdSBtaWdodCBtYWtlLgoKYGBge3IsIGZpZy53aWR0aCA9IDV9Cmdnc3BhdmlzOjpwbG90VmlzaXVtKHNwZSwgc3BvdHMgPSBGQUxTRSwgem9vbSA9IFRSVUUpIApgYGAKCkxldCdzIHRha2UgYSBtb21lbnQgdG8gY2hhdCBhYm91dCBXaWxtJ3MgVHVtb3IgLSB0aGVzZSB0dW1vcnMgaW4gdGhlIGRldmVsb3Bpbmcga2lkbmV5IGFyZSBvZnRlbiBjb21wb3NlZCBvZiBhIGNvdXBsZSBoaXN0b2xvZ2ljIGNvbXBhcnRtZW50cywgYmxhc3RlbWFsLCBzdHJvbWFsICh3aXRoIG1lc2VuY2h5bWFsIGJpb2xvZ3kpLCBhbmQgc29tZXRpbWVzIGVwaXRoZWxpYWwgY29tcG9uZW50cy4KSW4gdGhpcyBzZWN0aW9uLCB3ZSBjYW4gc2VlIHR3byBtYWpvciBjb21wb25lbnRzOiB0aGUgYmx1ZXIgaW5kaWNhdGluZyBkZW5zZWx5IGNlbGx1bGFyLCBFQ00tcG9vciByZWdpb25zIGNvbnNpc3RlbnQgd2l0aCBibGFzdGVtYSwgYW5kIHBhbGVyIHBpbmsgcmVnaW9ucyBjb25zaXN0ZW50IHdpdGggc3Ryb21hbCB0aXNzdWUuCgpMZXQncyBhY3R1YWxseSBnbyBhaGVhZCBhbmQgc2F2ZSB0aGlzIHBsb3QgdG8gYSB2YXJpYWJsZTsgaXQgd2lsbCBiZSBhIGNvbnZlbmllbnQgd2F5IGZvciB1cyB0byBxdWlja2x5IHBvcCB1cCB0aGUgSCZFIGZvciBzaWRlLWJ5LXNpZGUgY29tcGFyaXNvbiB3aXRoIG90aGVyIHBsb3RzIHdlJ2xsIG1ha2UgbGF0ZXIuCgpgYGB7cn0KcF9oZSA8LSBnZ3NwYXZpczo6cGxvdFZpc2l1bShzcGUsIHNwb3RzID0gRkFMU0UsIHpvb20gPSBUUlVFKSAKYGBgCgoKVGhlcmUncyBhbm90aGVyIGZ1bmN0aW9uIGNhbGxlZCBgcGxvdENvb3Jkc2Agd2hpY2ggaGlkZXMgdGhlIEgmRSB0byBqdXN0IHNob3cgdGhlIHNwb3RzOyB0aGlzIHBsb3QgaXMgbm90IGN1cnJlbnRseSB2ZXJ5IGNvbXBlbGxpbmcsIGJ1dCBvbmNlIHdlIHN0YXJ0IGNvbG9yaW5nIGJ5IHRoaW5ncyBpdCB3aWxsIGJlIG11Y2ggbW9yZSBmdW4hCgpgYGB7ciwgZmlnLndpZHRoID0gNX0KIyBubyBIJkUKZ2dzcGF2aXM6OnBsb3RDb29yZHMoc3BlLCBwb2ludF9zaXplID0gMSkgCmBgYAoKCiMjIFF1YWxpdHkgY29udHJvbAoKIyMjIFJlbW92aW5nIHVuaW5mb3JtYXRpdmUgYmlucwoKV2Ugb25seSBjYXJlIGFib3V0IHNwb3RzIG9uIHRvcCBvZiB0aXNzdWUsIHNvIGxldCdzIGJlZ2luIGJ5IHJlbW92aW5nIHRoZSBgaW5fdGlzc3VlID0gMGAgc3BvdHMuCihOb3RlIHRoYXQgcmVhZGluZyBpbiB0aGUgYGZpbHRlcmVkX2ZlYXR1cmVfYmNfbWF0cml4YCB2ZXJzaW9uIG9mIFNwYWNlIFJhbmdlciBvdXRwdXQgd291bGQgYWxyZWFkeSBiZSBmaWx0ZXJlZCB0byBvbmx5IGAxYCBpbiB0aGlzIGNvbHVtbiwgc28gd2UncmUgb25seSB0YWtpbmcgdGhpcyBzdGVwIGJlY2F1c2Ugd2UgcmVhZCBpbiB0aGUgYHJhd2Agb3V0cHV0KS4KCldlIGNhbiB2aXN1YWxpemUgd2hpY2ggc3BvdHMgdGhvc2UgYXJlLCBhbmQgd2UnbGwgZG8gaXQgb3ZlciB0aGUgSCZFIHRvIGNsZWFybHkgc2VlIHRoZSByZWxhdGlvbnNoaXAuCldlJ2xsIHVzZSB0aGUgYGFubm90YXRlYCBhcmd1bWVudCwgYnV0IGBwbG90VmlzaXVtYCBzZWVzIHRoYXQgdGhpcyBjb2x1bW4gaXMgYW4gaW50ZWdlciBhbmQgZm9yY2VzIGl0IHRvIHVzZSBhIGNvbnRpbnVvdXMgY29sb3Igc2NhbGU7IHdlJ2xsIGdvIGFoZWFkIGFuZCBtYWtlIGl0IGEgZmFjdG9yIGZvciBwbG90dGluZy4KCmBgYHtyLCBmaWcud2lkdGggPSA1fQojIG1ha2UgYSBmYWN0b3IgdmVyc2lvbiBvZiB0aGlzIGNvbHVtbiB0byBwbG90IHdpdGgKc3BlJGluX3Rpc3N1ZV9mYWN0b3IgPC0gYXMuZmFjdG9yKHNwZSRpbl90aXNzdWUpCgpnZ3NwYXZpczo6cGxvdFZpc2l1bSgKICBzcGUsIAogIGFubm90YXRlID0gImluX3Rpc3N1ZV9mYWN0b3IiLCAKICAjIGN1c3RvbSBwYWxldHRlIHNvIHdlIGNhbiBzZWUgY2xlYXJseQogIHBhbCA9IGMoInJlZCIsICJsaWdodGJsdWUiKQopCmBgYAoKLSBUaGUgdGlzc3VlIG92ZXJoYW5nIG9uIHRoZSBsZWZ0IGlzbid0IGNvbG9yZWQgYXQgYWxsIC0gaW5kZWVkLCB0aGVyZSBhcmVuJ3Qgc3BvdHMgYXQgdGhvc2UgY29vcmRpbmF0ZXMgb3V0c2lkZSB0aGUgc2xpZGUKLSBSZWQgcG9pbnRzIGFyZSB0aG9zZSB0byBmaWx0ZXIgb3V0IC0gdGhleSBhcmUgdW5pbmZvcm1hdGl2ZS4KV29ydGggbm90aW5nIHRoYXQgc29tZSBvZiB0aGVzZSBhcHBlYXIgImluc2lkZSIgdGhlIHRpc3N1ZS4gCgpgYGB7cn0KIyBrZWVwIG9ubHkgc3BvdHMgdGhhdCBhcmUgaW4gdGhlIHRpc3N1ZQpzcGUgPC0gc3BlWywgc3BlJGluX3Rpc3N1ZSA9PSAxXQpzcGUKYGBgCgpOb3csIHdlJ3JlIGRvd24gdG8gMzcwMSBzcG90cyBhbmQgd2UgY2FuIHNlZSB0aGlzIHJlZmxlY3RlZCBpbiBhIHBsb3QuCldlIG5vdyBvbmx5IHNlZSBzcG90cyB3aXRoIHRpc3N1ZSB1bmRlcm5lYXRoLiAKT253YXJkcyEKCmBgYHtyLCBmaWcud2lkdGggPSA1fQpnZ3NwYXZpczo6cGxvdFZpc2l1bShzcGUpCmBgYAoKIyMjIEZpbHRlcmluZyBmb3IgYmluIHF1YWxpdHkKCgpXZSBfY291bGRfIGRvIHRoZSBzYW1lIGtpbmQgb2YgUUMgdGhhdCB3ZSBkbyBmb3Igc2luZ2xlLWNlbGwgZGF0YSwgd2hpY2ggKHVzdWFsbHkpIGludm9sdmVzIGRlZmluaW5nIGdsb2JhbCB0aHJlc2hvbGRzIGZvciBlLmcuIG51bWJlciBvZiBkZXRlY3RlZCBVTUlzIG9yIHBlcmNlbnQgbWl0by4KSG93ZXZlciwgc3BhdGlhbCBkYXRhIGRvZXNuJ3QgbGVuZCBpdHNlbGYgYXMgd2VsbCB0byB0aGlzIHR5cGUgb2YgZmlsdGVyaW5nIHNpbmNlIGdsb2JhbCB0aHJlc2hvbGRzIG1heSBub3QgYmUgc3VpdGFibGUgZm9yIHRoZSBkaWZmZXJlbnQgdGlzc3Vlcy4KQ29tcGFyZWQgdG8gc3BhdGlhbCBkYXRhLCBzaW5nbGUtY2VsbCBkYXRhIG1vcmUgY2xvc2VseSAoYnV0IG9mIGNvdXJzZSBub3QgZXhhY3RseSEpIHJlc2VtYmxlcyBhIGhvbW9nZW5lb3VzIHBvb2wgb2YgY2VsbHMuIAoKCkxldCdzIGdvIGFoZWFkIGFuZCBjYWxjdWxhdGUgc29tZSBRQyBzdGF0cyBhbmQgdmlzdWFsaXplIHRoZW06CgpgYGB7cn0KaXNfbWl0byA8LSBncmVwbCgiKF5NVC0pfChebXQtKSIsIHJvd0RhdGEoc3BlKSRTeW1ib2wpCm1pdG9fZW5zZW1ibCA8LSByb3duYW1lcyhzcGUpW2lzX21pdG9dCnNwZSA8LSBzY2F0ZXI6OmFkZFBlckNlbGxRQyhzcGUsIHN1YnNldHM9bGlzdChtaXRvPW1pdG9fZW5zZW1ibCkpCmBgYAoKYGdnc3BhdmlzYCBjb21lcyB3aXRoIGEgaGVscGZ1bCBRQyBwbG90dGVyIChtYWtlcyBoaXN0b2dyYW1zIGJ5IGRlZmF1bHQgYnV0IGhhcyBhIGNvdXBsZSBtb3JlIG9wdGlvbnMhKQoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA0fQpwMSA8LSBnZ3NwYXZpczo6cGxvdE9ic1FDKHNwZSwgeF9tZXRyaWMgPSAic3VtIikgKyBnZ3RpdGxlKCJzdW0iKQpwMiA8LSBnZ3NwYXZpczo6cGxvdE9ic1FDKHNwZSwgeF9tZXRyaWMgPSAiZGV0ZWN0ZWQiKSArIGdndGl0bGUoImRldGVjdGVkIikKcDMgPC0gZ2dzcGF2aXM6OnBsb3RPYnNRQyhzcGUsIHhfbWV0cmljID0gInN1YnNldHNfbWl0b19wZXJjZW50IikgKyBnZ3RpdGxlKCJtaXRvICUiKQoKcDEgKyBwMiArIHAzCmBgYAoKRGlzdHJpYnV0aW9ucyBkb24ndCBsb29rIGxpa2UgdGhlcmUgYXJlIGFueSBfbWFqb3JfIGlzc3VlcyB3aXRoIHF1YWxpdHkgaGVyZS4KT25lIHJlYXNvbiBmb3IgdGhpcyBpcyAtIHRoZXNlIGFyZSBhbGwgc3BvdHMgKGFnZ3JlZ2F0ZXMgb2YgY2VsbHMpLCBub3QgY2VsbHMhIApEYXRhIGlzIHNwYXJzZSwgYnV0IG5vdCBxdWl0ZSBhcyBzcGFyc2UuCgpMZXQncyBwbG90IHRoZSBzYW1lIHN0YXRzIG9uIHRoZSBzbGlkZToKCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KcDEgPC0gcGxvdENvb3JkcyhzcGUsIGFubm90YXRlPSJzdW0iLCBwb2ludF9zaXplID0gMSkgKyBnZ3RpdGxlKCJzdW0iKQpwMiA8LSBwbG90Q29vcmRzKHNwZSwgYW5ub3RhdGU9ImRldGVjdGVkIiwgcG9pbnRfc2l6ZSA9IDEpICsgZ2d0aXRsZSgiZGV0ZWN0ZWQiKQpwMyA8LSBwbG90Q29vcmRzKHNwZSwgYW5ub3RhdGU9InN1YnNldHNfbWl0b19wZXJjZW50IiwgcG9pbnRfc2l6ZSA9IDEpICsgZ2d0aXRsZSgibWl0byAlIikKcDEgKyBwMiArIHAzICsgcF9oZQpgYGAKCldlIF9kb18gc2VlIGEgcmVsYXRpb25zaGlwIGhlcmUgd2l0aCB0aXNzdWUgdHlwZSBhbmQgUUMgc3RhdHMgdGhhdCBzdWdnZXN0cyB3ZSBkb24ndCBoYXZlIGdsb2JhbGx5IGhvbW9nZW5lb3VzIHBhdHRlcm5zIC0gCgotIHRoZSBsaWtlbHkgc3Ryb21hbCByZWdpb25zIGxvb2sgbGlrZSB0aGV5IGhhdmUgcmVsYXRpdmVseSBoaWdoZXIgbWl0byAoYWx0aG91Z2ggaXQncyBsb3cgYWxsIGFyb3VuZCEpCi0gdGhlIGxpa2VseSBibGFzdGVtYSByZWdpb25zIHRlbmQgdG8gaGF2ZSBtb3JlIGRldGVjdGVkIGdlbmVzCgpUaGlzIHRlbGxzIHVzIHRoYXQgdGhlcmUgaXMgbG9jYWwgc3RydWN0dXJlIGluIHRoZSBkYXRhLCBhbmQgdXNpbmcgZ2xvYmFsIHRocmVzaG9sZHMgY291bGQgYmUgdG9vIGFnZ3Jlc3NpdmUvbm90IGFnZ3Jlc3NpdmUgZW5vdWdoIGRlcGVuZGluZyBvbiB0aGUgbG9jYWwgY29udGV4dC4KCiMjIyBMb2NhbCBmaWx0ZXJpbmcgd2l0aCBTcG90IFN3ZWVwZXIKCkEgbW9yZSBzcGF0aWFsbHktc3VpdGVkIGFwcHJvYWNoIGNvbWVzIGZyb20gYFNwb3RTd2VlcGVyYCB3aGljaCBtb2RlbHMgUUMgc3RhdHMgdG8gaWRlbnRpZnkgX2xvY2FsXyBvdXRsaWVycy4KCldlJ2xsIGRldGVjdCBsb2NhbCBvdXRsaWVycyBiYXNlZCBvbiB0aGVzZSB0aHJlZSBRQyBzdGF0cy4KVGhpcyBmdW5jdGlvbiB3aWxsIGFkZCBzb21lIGBjb2xEYXRhYCBjb2x1bW5zIHdlIHdlIGNhbiB1c2UgdG8gZmluZCBhbGwgdGhlIGxvY2FsIG91dGxpZXJzLgoKYGBge3J9CnNwZSA8LSBTcG90U3dlZXBlcjo6bG9jYWxPdXRsaWVycyhzcGUsIG1ldHJpYz0ic3VtIiwgZGlyZWN0aW9uPSJsb3dlciIsIGxvZz1UUlVFKSAjIGxvd2VyLXZhbHVlIG91dGxpZXJzIHNob3VsZCBiZSBkZXRlY3RlZApzcGUgPC0gU3BvdFN3ZWVwZXI6OmxvY2FsT3V0bGllcnMoc3BlLCBtZXRyaWM9ImRldGVjdGVkIiwgZGlyZWN0aW9uPSJsb3dlciIsIGxvZz1UUlVFKSAjIGxvd2VyLXZhbHVlIG91dGxpZXJzIHNob3VsZCBiZSBkZXRlY3RlZApzcGUgPC0gU3BvdFN3ZWVwZXI6OmxvY2FsT3V0bGllcnMoc3BlLCBtZXRyaWM9InN1YnNldHNfbWl0b19wZXJjZW50IiwgZGlyZWN0aW9uPSJoaWdoZXIiLCBsb2c9RkFMU0UpICMgaGlnaGVyLXZhbHVlIG91dGxpZXJzIHNob3VsZCBiZSBkZXRlY3RlZApgYGAgCgpIb3cgbWFueSBvZiBlYWNoIGtpbmQgb2Ygb3V0bGllcj8KCmBgYHtyfQpjKCJzdW1fb3V0bGllcnMiLCAiZGV0ZWN0ZWRfb3V0bGllcnMiLCAic3Vic2V0c19taXRvX3BlcmNlbnRfb3V0bGllcnMiKSB8PgogIHB1cnJyOjpzZXRfbmFtZXMoKSB8PgogIHB1cnJyOjptYXAoXCh4KSB0YWJsZShzcGVbW3hdXSkpCmBgYAoKV2UnbGwgdXNlIGEgYnVpbHQtaW4gcGxvdHRpbmcgZnVuY3Rpb24gZnJvbSBgU3BvdFN3ZWVwZXJgIHRvIHBsb3QgdGhlc2UgcmVzdWx0cyAodGhlIGBnZ3NwYXZpc2AgcGxvdHRpbmcgaXNuJ3QgcXVpdGUgYXMgbmljZSBpbiB0aGlzIGNhc2UpLgpFYWNoIHBsb3QgaGlnaGxpZ2h0cyB0aGUgc3BvdHMgdG8gcmVtb3ZlLCBhbmQgc3BvdHMgYXJlIGNvbG9yZWQgYmFzZWQgb24gYSBkaWZmZXJlbnQgc3RhdC4KQWdhaW4gd2UnbGwgaW5jbHVkZSB0aGUgSCZFIGFzIGEgcGFuZWwgZm9yIGVhc2Ugb2YgY29tcGFyaXNvbi4KCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KcDEgPC0gU3BvdFN3ZWVwZXI6OnBsb3RRQ21ldHJpY3Moc3BlLCBtZXRyaWMgPSAic3VtX2xvZyIsIG91dGxpZXJzID0gInN1bV9vdXRsaWVycyIpICsgZ2d0aXRsZSgibG9nIHN1bSIpCnAyIDwtIFNwb3RTd2VlcGVyOjpwbG90UUNtZXRyaWNzKHNwZSwgbWV0cmljID0gImRldGVjdGVkX2xvZyIsIG91dGxpZXJzID0gImRldGVjdGVkX291dGxpZXJzIikgKyBnZ3RpdGxlKCJsb2cgZGV0ZWN0ZWQiKQpwMyA8LSBTcG90U3dlZXBlcjo6cGxvdFFDbWV0cmljcyhzcGUsIG1ldHJpYyA9ICJzdWJzZXRzX21pdG9fcGVyY2VudCIsIG91dGxpZXJzID0gInN1YnNldHNfbWl0b19wZXJjZW50X291dGxpZXJzIikgKyBnZ3RpdGxlKCJtaXRvICUiKQoKcDEgKyBwMiArIHAzICsgcF9oZQpgYGAKU29tZSBvZiB0aGVzZSBzcG90cyBhcmUgYmVpbmcgZmxhZ2dlZCBieSBtdWx0aXBsZSBzdGF0cywgaW4gcGFydGljdWxhciBib3RoIGBzdW1gIGFuZCBgZGV0ZWN0ZWRgLgpMZXQncyBnbyBhaGVhZCBhbmQgZmlsdGVyIGFsbCB0aGVzZSBjZWxscyBvdXQ6CgpgYGB7cn0KIyBjb21iaW5lIGFsbCBvdXRsaWVycyBpbnRvICJsb2NhbF9vdXRsaWVycyIgY29sdW1uIHRvIGZpbHRlciBvbgpzcGUkbG9jYWxfb3V0bGllcnMgPC0gYXMubG9naWNhbChzcGUkc3VtX291dGxpZXJzKSB8CiAgICBhcy5sb2dpY2FsKHNwZSRkZXRlY3RlZF9vdXRsaWVycykgfAogICAgYXMubG9naWNhbChzcGUkc3Vic2V0c19taXRvX3BlcmNlbnRfb3V0bGllcnMpCgpzcGUgPC0gc3BlWywgc3BlJGxvY2FsX291dGxpZXJzID09IEZBTFNFXQpgYGAKCgoKCiMjIE5vcm1hbGl6YXRpb24KCldpdGggc2luZ2xlLWNlbGwsIGEgcG9wdWxhciBub3JtYWxpemF0aW9uIGFwcHJvYWNoIHdlIGFkb3B0IGludm9sdmVzIGRlY29udm9sdXRpb24gLSB3ZSBkbyBhIHF1aWNrIGNsdXN0ZXJpbmcgc28gd2UgY2FuIGdldCBncm91cHMgb2YgY2VsbHMgdGhhdCB3aWxsIHNoYXJlIGEgc2l6ZSBmYWN0b3IsIGZvciBmYXN0ZXIgcHJvY2Vzc2luZy4KQnV0IHRoaXMgaXNuJ3Qgc3VpdGFibGUgZm9yIHNwb3QtYmFzZWQgc3BhdGlhbCBkYXRhIHNpbmNlIG91ciB1bml0cyBhcmVuJ3QgY2VsbHMsIGJ1dCBwb3RlbnRpYWxseSBoZXRlcm9nZW5lb3VzIGdyb3VwcyBvZiBjZWxscyAod2hvbGUgb3IgcGFydGlhbCkuCgpXZSdsbCB0aGVyZWZvcmUgdXNlIGdvaW5nIHRvIHVzZSBsaWJyYXJ5LXNpemUgbm9ybWFsaXphdGlvbiBoZXJlIHRvIGNvcnJlY3QgZm9yIHNlcXVlbmNpbmcgZGVwdGggYnV0IHByZXNlcnZlIHRoZSBvdGhlciBoZXRlcm9nZW5lb3VzIGJpb2xvZ2ljYWwgc2lnbmFsLgpCZWFyIGluIG1pbmQgdGhlIGNhdmVhdCB0aGF0IGluIHNvbWUgY2FzZXMgbGlicmFyeSBzaXplIGl0c2VsZiBpcyBbYWxzbyBjb25mb3VuZGVkIHdpdGggc3BhdGlhbCBzdHJ1Y3R1cmVdKChodHRwczovL2xpbmsuc3ByaW5nZXIuY29tL2FydGljbGUvMTAuMTE4Ni9zMTMwNTktMDI0LTAzMjQxLTcpOyBzbywga2VlcCB5b3VyIGV5ZXMgcGVlbGVkIGZvciBub3JtYWxpemF0aW9uIGRldmVsb3BtZW50cywgc2luY2UgdGhpcyBzcGFjZSBpcyBldm9sdmluZyByYXBpZGx5IQoKYGBge3J9CnNwZSA8LSBzY3V0dGxlOjpjb21wdXRlTGlicmFyeUZhY3RvcnMoc3BlKQpzcGUgPC0gc2N1dHRsZTo6bG9nTm9ybUNvdW50cyhzcGUpCnNwZSAjIG5vdyB3ZSBoYXZlIGEgbG9nY291bnRzIGFzc2F5CmBgYAoKIyMgRGltZW5zaW9uIHJlZHVjdGlvbgoKRm9yIHRoaXMgc3RlcCwgd2UncmUgZ29pbmcgdG8gZ2V0IGEgcmVkdWNlZCBkaW1lbnNpb24gcmVwcmVzZW50YXRpb24gdXNpbmcgX2p1c3RfIHRoZSBsZW5zIG9mIGdlbmUgZXhwcmVzc2lvbiwgImlnbm9yaW5nIiBzcGF0aWFsIGluZm9ybWF0aW9uLgooTGF0ZXIsIHdlJ2xsIHNlZSBjb21wbGVtZW50YXJ5IGFwcHJvYWNoZXMgdG8gc29tZSBvZiB0aGlzIHdoaWNoIGxldmVyYWdlcyBzcGF0aWFsIGluZm9ybWF0aW9uKS4KClRoaXMgYWxsIHRlbGxzIHVzIHdlIGhhdmUgdG8gYmUgY2FyZWZ1bCB3aGVuIGludGVycHJldGluZyBhbmFseXNlcyBvbiBzcGF0aWFsIGRhdGE6ICJ0byB3aGF0IGV4dGVudCB3YXMgc3BhdGlhbCBpbmZvcm1hdGlvbiB0YWtlbiBpbnRvIGFjY291bnQiIGlzIHNvbWV0aGluZyB5b3UnbGwgd2FudCB0byB1bmRlcnN0YW5kLgoKTGV0J3MgZG8gSFZHIHNlbGVjdGlvbiAoYWdhaW4sIHRoaXMgZG9lcyBub3QgY29uc2lkZXIgc3BhdGlhbCBwYXR0ZXJucyEgd2UnbGwgbGVhcm4gYWJvdXQgX3NwYXRpYWxseV8gdmFyaWFibGUgZ2VuZXMgbGF0ZXIpLCBQQ0EsIFVNQVAsIGFuZCBjbHVzdGVyaW5nIChvbmNlIGFnYWluLCB0aGlzIHdvbid0IGJlIHNwYXRpYWwgcmVnaW9ucyEganVzdCBfc3BvdF8gc2ltaWxhcml0eSBiYXNlZCBvbiBleHByZXNzaW9uIC0gdGhhdCdzIG5vdCB0aGUgc2FtZSBhcyBjZWxscywgZWl0aGVyISkKU28sIHdlJ2xsIHVzZSBhIGBTQ0VgLXN0eWxlIGFwcHJvYWNoOgoKCmBgYHtyfQpudW1fZ2VuZXMgPC0gMjAwMApnZW5lX3ZhcmlhbmNlIDwtIHNjcmFuOjptb2RlbEdlbmVWYXIoc3BlKQpodl9nZW5lcyA8LSBzY3Jhbjo6Z2V0VG9wSFZHcyhnZW5lX3ZhcmlhbmNlLCBuID0gbnVtX2dlbmVzKQoKc3BlIDwtIHNjYXRlcjo6cnVuUENBKHNwZSwgc3Vic2V0X3JvdyA9IGh2X2dlbmVzKQpzcGUgPC0gc2NhdGVyOjpydW5VTUFQKHNwZSwgZGltcmVkID0gIlBDQSIpCgpubl9jbHVzdGVycyA8LSBzY3Jhbjo6Y2x1c3RlckNlbGxzKAogIHNwZSwgIyBvYmplY3QgdG8gcGVyZm9ybSBjbHVzdGVyaW5nIG9uCiAgdXNlLmRpbXJlZCA9ICJQQ0EiLCAjIHBlcmZvcm0gY2x1c3RlcmluZyBvbiB0aGUgUENBIG1hdHJpeAogIEJMVVNQQVJBTSA9IGJsdXN0ZXI6Ok5OR3JhcGhQYXJhbSgKICAgIGsgPSAyMCwKICAgIHR5cGUgPSAiamFjY2FyZCIsCiAgICBjbHVzdGVyLmZ1biA9ICJsb3V2YWluIgogICkKKQpzcGUkbm5fY2x1c3RlciA8LSBubl9jbHVzdGVycwpgYGAKCgpXZSdsbCBwbG90IHRoZSBVTUFQIGNvbG9yZWQgYnkgY2x1c3RlcnMsIGJlY2F1c2Ugd2UgbG92ZSBVTUFQczoKCmBgYHtyfQpzY2F0ZXI6OnBsb3RVTUFQKHNwZSwgY29sb3JfYnkgPSAibm5fY2x1c3RlciIpICsKICAjIHVzZSBkaXN0aW5jdCBwYWxldHRlIHNvIHdlIGNhbiBtYXRjaCB1cCB3aXRoIGEgc3BhdGlhbCB2aWV3IG5leHQKICBnZ29rYWJlaXRvOjpzY2FsZV9jb2xvcl9va2FiZV9pdG8oKQpgYGAKCkhvdyBkbyB0aGVzZSBjbHVzdGVycyBjb21lIG91dCB3aGVuIHVzaW5nIHRoZSBzcGF0aWFsIGxheW91dD8KV2UgaGF2ZSBjb29yZGluYXRlcywgYWZ0ZXIgYWxsIC0gbGV0J3MgdXNlIHRoZW0hCgpgYGB7ciwgZmlnLndpZHRoID0gOH0KcDEgPC0gZ2dzcGF2aXM6OnBsb3RDb29yZHMoc3BlLCBhbm5vdGF0ZSA9ICJubl9jbHVzdGVyIiwgcG9pbnRfc2l6ZSA9IDEpICsKICAjIG1hdGNoaW5nIHBhbGV0dGUgdG8gVU1BUAogIGdnb2thYmVpdG86OnNjYWxlX2NvbG9yX29rYWJlX2l0bygpCgpwMSArIHBfaGUKYGBgCgpBIGxvdCBvZiB0aGUgY2x1c3RlcnMgYXJlIGludGVybWluZ2xlZCBpbiBzcGFjZSwgc2hvd2luZyB0aGF0IHRoaXMgYW5hbHlzaXMgaXMgcmVhbGx5IG5vdCBzcGF0aWFsbHktYXdhcmUhCkJ1dCwgd2UgZG8gc2VlIHNvbWUgaW50ZXJlc3RpbmcgcGF0dGVybnMgLSBjaGVjayBvdXQgY2x1c3RlcnMgMSwgNSwgJiA3IGluIHBhcnRpY3VsYXIgLSB0aGV5IGxvb2sgc3Ryb21hLXk7IGFsc28sIGNsdXN0ZXIgOCBvb2tzIGxpa2UgYSBkZWZpbmVkIHJlZ2lvbiBpbiB0aGUgSCZFLCBidXQgSSdtIG5vdCBhIHBhdGhvbG9naXN0IHNvIGxldCdzIG5vdCBvdmVyaW50ZXJwcmV0IGhlcmUuCldoZW4gZXhwcmVzc2lvbiBhbmQgdGlzc3VlIHN0cnVjdHVyZSBhcmUgY29ycmVsYXRlZCwgZXhwcmVzc2lvbi1vbmx5IGNsdXN0ZXJzIGNhbiBwaWNrIHVwIG9uIGNlcnRhaW4gc3BhdGlhbCByZWdpb25zLCBidXQgdGhpcyBpcyBfbm90XyB0aGUgc2FtZSBhcyBmaW5kaW5nICJ0cnVlIiBzcGF0aWFsIGRvbWFpbnMuIAoKCiMjIEV4cGxvcmluZyBzcG90LWJhc2VkIHNwYXRpYWwgZGF0YQoKTGV0J3MgZXhwbG9yZSB0aGlzIGRhdGEgYSBsaXR0bGUgbW9yZSwgdXNpbmcgc29tZSBtYXJrZXIgZ2VuZXMuCldlIGRvbid0IGhhdmUgc3BlY2lmaWMgY2VsbHMgYXMgdW5pdHMgaGVyZSwgYnV0IHdlIGNhbiB1c2UgbWFya2VyIGdlbmUgZXhwcmVzc2lvbiB0byBidWlsZCBzb21lIGRlc2NyaXB0aXZlIGludHVpdGlvbiBmb3IgdGhlIGRhdGEuIAoKV2UndmUgZGVmaW5lZCBzb21lIG1hcmtlciBnZW5lcyBoZXJlIGZvciB2aXN1YWxpemF0aW9uIHRoYXQgY29ycmVzcG9uZCB0byBleHBlY3RlZCBXaWxtJ3MgVHVtb3IgYmlvbG9neToKCmBgYHtyfQptYXJrZXJfZ2VuZXMgPC0gYygKICAiV1QxIChraWRuZXkgZGV2ZWxvcG1lbnQsIG1hZ2xpbmFudCBpbiBXVCkiID0gIkVOU0cwMDAwMDE4NDkzNyIsCiAgIlNJWDEgKGJsYXN0ZW1hLCBtYWxpZ25hbnQpIiA9ICJFTlNHMDAwMDAxMjY3NzgiLAogICJNWUNOIChtYWxpZ25hbnQpIiA9ICJFTlNHMDAwMDAxMzQzMjMiLAogICJDT0wxQTEgKGZpYnJvYmxhc3QvbWVzZW5jaHltYWwpIiA9ICJFTlNHMDAwMDAxMDg4MjEiLAogICJUQUdMTiAobXlvZmlicm9ibGFzdC9tZXNlbmNoeW1hbCkiID0gIkVOU0cwMDAwMDE0OTU5MSIKKQpgYGAKCkxldCdzIHBsb3QgdGhlIGV4cHJlc3Npb24gb2YgdGhlc2UgZ2VuZXMsIHVzaW5nIGJvdGggVU1BUCBhbmQgdGhlIHNwYXRpYWwgbGF5b3V0cyB0byBjb21wYXJlOgoKYGBge3IsIGZpZy53aWR0aCA9IDE0fQpjbHVzdGVyX3VtYXAgPC0gc2NhdGVyOjpwbG90VU1BUChzcGUsIGNvbG9yX2J5ID0gIm5uX2NsdXN0ZXIiLCBwb2ludF9zaXplID0gMC41LCBwb2ludF9hbHBoYSA9IDAuNSkgKwogIGdndGl0bGUoImV4cHJlc3Npb24tYmFzZWQgY2x1c3RlcnMiKSArIAogIGNvb3JkX2VxdWFsKCkgKwogIGdnb2thYmVpdG86OnNjYWxlX2NvbG9yX29rYWJlX2l0bygpIAoKbWFya2VyX3VtYXBzIDwtIG1hcmtlcl9nZW5lcyB8PgogIHB1cnJyOjppbWFwKAogICAgXChlbnNlbWJsLCBnZW5lX25hbWUpIHsKICAgICAgc2NhdGVyOjpwbG90VU1BUChzcGUsIGNvbG9yX2J5ID0gZW5zZW1ibCwgcG9pbnRfc2l6ZSA9IDAuNSwgcG9pbnRfYWxwaGEgPSAwLjUpICsgZ2d0aXRsZShnZW5lX25hbWUpICsgY29vcmRfZXF1YWwoKQogICAgfQogICkgfD4KICAjIGNsdXN0ZXIgdW1hcCBhbmQgdGhlbiB0aGUgbWFya2VyIGdlbmUgdW1hcHMKICBhcHBlbmQodmFsdWVzID0gY2x1c3Rlcl91bWFwLCBhZnRlciA9IDApCgpwYXRjaHdvcms6OndyYXBfcGxvdHMobWFya2VyX3VtYXBzLCBucm93ID0gMikKYGBgCgoKYGBge3IsIGZpZy53aWR0aCA9IDEyfQpjbHVzdGVyX2Nvb3JkcyA8LSBnZ3NwYXZpczo6cGxvdENvb3JkcyhzcGUsIGFubm90YXRlID0gIm5uX2NsdXN0ZXIiLCBwb2ludF9zaXplID0gMC43NSkgKwogIGdndGl0bGUoImV4cHJlc3Npb24tYmFzZWQgY2x1c3RlcnMiKSArIAogIGdnb2thYmVpdG86OnNjYWxlX2NvbG9yX29rYWJlX2l0bygpIAoKbWFya2VyX2Nvb3JkcyA8LSBtYXJrZXJfZ2VuZXMgfD4KICBwdXJycjo6aW1hcCgKICAgIFwoZW5zZW1ibCwgZ2VuZV9uYW1lKSB7CiAgICAgICMgZGVmYXVsdCBhc3NheSBpcyBjb3VudHMsIG92ZXJyaWRlIQogICAgICBnZ3NwYXZpczo6cGxvdENvb3JkcyhzcGUsIGFubm90YXRlID0gZW5zZW1ibCwgcG9pbnRfc2l6ZSA9IDAuNzUsIGFzc2F5ID0gImxvZ2NvdW50cyIpICsKICAgICAgICBzY2FsZV9jb2xvcl9kaXN0aWxsZXIocGFsZXR0ZSA9ICJCbHVlcyIsIGRpcmVjdGlvbiA9IDEpICsgCiAgICAgICAgZ2d0aXRsZShnZW5lX25hbWUpCiAgICB9CiAgKSB8PgogICMgY2x1c3RlciBjb29vcmRzIGFuZCB0aGVuIHRoZSBtYXJrZXIgZ2VuZSBjb29yZHMKICBhcHBlbmQodmFsdWVzID0gY2x1c3Rlcl9jb29yZHMsIGFmdGVyID0gMCkgCgpwX2hlICsgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKG1hcmtlcl9jb29yZHMsIG5yb3cgPSAyKSArIHBsb3RfbGF5b3V0KHdpZHRocyA9IGMoMSwyKSkKYGBgCgoKQmFzZWQgb24gdGhlc2Ugb2JzZXJ2YXRpb25zLCBpdCBzZWVtcyBsaWtlIHRoZXJlIGNvdWxkIGJlIHNvbWUgc3BhdGlhbCBwYXR0ZXJucy92YXJpYWJpbGl0eSBoZXJlOyBfaW50ZXJwcmV0Xy4KV2UnbGwgdGFsayBtb3JlIHJpZ29yb3VzbHkgYWJvdXQgcXVhbnRpZnlpbmcgc3BhdGlhbGx5LXZhcmlhYmxlIGdlbmVzIGxhdGVyIGluIHRoZSB3b3Jrc2hvcCwgYnV0IGZvciBub3csIHdlIGNhbiB0YWtlIHRoaXMgb3Bwb3J0dW5pdHkgdG8gdG8gYXNrIHNvbWUgZGVzY3JpcHRpdmUgcXVlc3Rpb25zIGFib3V0IHRoZXNlIGdlbmVzJyBleHByZXNzaW9uIGFjcm9zcyBzcGFjZS4KCgoKIyMjIFNwYXRpYWwgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyAKCkFsdGhvdWdoIHNwYXRpYWwgYW5hbHlzaXMgaXMgYW4gZW1lcmdpbmcgZmllbGQgaW4gdHJhbnNjcmlwdG9taWNzLCBpdCdzIG5vdCBuZXcgdG8gc2NpZW5jZSEKRm9sa3MgaW4gZ2VvZ3JhcGh5LCBlcGlkZW1pb2xvZ3ksIGVjb2xvZ3ksIGFuZCBtb3JlIGhhdmUgYmVlbiB0aGlua2luZyBhYm91dCBzcGF0aWFsIGRhdGEgZm9yIGEgbG9uZyB0aW1lLCBzbyB3ZSBjYW4gZHJhdyBmcm9tIGV4aXN0aW5nIHF1YW50aXRhdGl2ZSBhcHByb2FjaGVzLgoKU3BvdC1iYXNlZCBzcGF0aWFsIGRhdGEgaXMgYW5hbG9nb3VzIHRvICJsYXR0aWNlLXN0cnVjdHVyZSIgc3BhdGlhbCBkYXRhLCBpbiBjb250cmFzdCB0byBoaWdoLXJlcyBzcGF0aWFsIHRlY2hub2xvZ2llcyB0aGF0IGFyZSAicG9pbnQtcGF0dGVybiIgZGF0YTsgc2VlIFt0aGUgcGFzdGEgcGFwZXJdKGh0dHBzOi8vYWNhZGVtaWMub3VwLmNvbS9uYXIvYXJ0aWNsZS81My8xNy9na2FmODcwLzgyNTA0NzcpLgoKVGhlIFIgcGFja2FnZSBgc3BkZXBgIGFscmVhZHkgZXhpc3RzIHRvIHdvcmsgd2l0aCBsYXR0aWNlLXN0cnVjdHVyZSBzcGF0aWFsIGRhdGEsIGFuZCBoZWxwZnVsbHkgZm9sa3MgaGF2ZSBhbHJlYWR5IHN0YXJ0ZWQgdG8gcHV0IHRoaXMgKGFuZCBvdGhlciBzcGF0aWFsIHBsYXRmb3JtcykgdG8gd29yayBmb3IgdHJhbnNjcmlwdG9taWNzIGRhdGEuClRoZSBgVm95YWdlcmAvYFNwYXRpYWxGZWF0dXJlRXhwZXJpbWVudGA6IDxodHRwczovL3BhY2h0ZXJsYWIuZ2l0aHViLmlvL3ZveWFnZXIvaW5kZXguaHRtbD4gZnJhbWV3b3JrIGlzIGNvbXBsZW1lbnRhcnkgdG8gYFNwYXRpYWxFeHBlcmltZW50YCBhbmQgY2FuIGhlbHAgdXMgdXNlIHRoZXNlIHRvb2xzLgpUaGUgYFNGRWAgb2JqZWN0IGlzIGFuIGV4cGFuZGVkIGBTUEVgIG9iamVjdCB3aXRoIG1vcmUgc2xvdHMgdG8gZmFjaWxpdGF0ZSBzcGF0aWFsIGFuYWx5c2lzIHdpdGggZXhpc3RpbmcgdG9vbHMgKHdlIHdvbid0IGdldCBpbnRvIHRoZXNlIHdlZWRzIG11Y2ggdGhvdWdoLCBzdWZmaWNlIHRvIHNheSBpdCdzIGEgc3VwZXIgcmljaCBlbnZpcm9ubWVudCkuCgpXaGVuIHdvcmtpbmcgd2l0aCBzcG90IGRhdGEsIHdlIGNhbiB0dXJuIGl0IGludG8gYSBncmFwaCBhbmQgYXNrIGRlc2NyaXB0aXZlIChub3QgbW9kZWxpbmchKSBxdWVzdGlvbnMgYWJvdXQgc3BhdGlhbCBzdHJ1Y3R1cmUgZGlyZWN0bHkuCkxldCdzIGdvIQoKRmlyc3QsIHdlJ2xsIG5lZWQgdG8gZ2V0IGl0IGludG8gdGhlIGBTRkVgIGZyYW1ld29yayBhbmQgY29udmVydCB0aGUgYFNQRWAgdG8gYW4gYFNGRWAgb2JqZWN0LgoKYGBge3J9CiMgVE9ETzogSSBoYXZlIHRvIHJlbW92ZSB0aGUgaW1hZ2UgYmVmb3JlIGNvbnZlcnRpbmcsIGVycm9ycyBvdGhlcndpc2UuIENhbiB0aGlzIGJlIGF2b2lkZWQ/CiMgRXJyb3I6IFtleHRdIGludmFsaWQgZXh0ZW50CnNwZV9ub2ltZyA8LSBzcGUKaW1nRGF0YShzcGVfbm9pbWcpIDwtIGltZ0RhdGEoc3BlX25vaW1nKVswLCAsIGRyb3A9RkFMU0VdCnNmZSA8LSB0b1NwYXRpYWxGZWF0dXJlRXhwZXJpbWVudChzcGVfbm9pbWcpCiNzZmUgPC0gbWlycm9yKHNmZSwgZGlyZWN0aW9uID0gInZlcnRpY2FsIikgIyB1c2UgbWlycm9yIHNvIHBsb3RzIGJldHdlZW4gZ2dzcGF2aXMgYW5kIHRoaXMgb25lIG1hdGNoLiAKIyBPUiwgbGV0IGl0IGJlIGEgbWlycm9yIHRvIHNlcnZlIGFzIGEgdGVhY2hhYmxlIG1vbWVudCBhYm91dCBzcGFjZSEKYGBgCgpPbmUgd2F5IHRvIG1ha2UgYSBncmFwaCBpcyB1c2luZyBrLW5lYXJlc3QgbmVpZ2hib3JzLCBidXQgdGhlcmUncyBvdGhlciB3YXlzIChhbGwgb2YgdGhlc2UgY2hvaWNlcyBtYXR0ZXIgdG8gc29tZSBkZWdyZWUsIGFuZCB3ZSBzaG91bGQgZGlzY3VzcyB0aGF0ISk6Ck5vdyB3ZSdsbCBtYWtlIGEgZ3JhcGggdXNpbmc6CgotIHNwb3QgY2VudHJvaWRzIGFzIG5vZGVzIChkb24ndCBza2lwIHRoYXQgdGhpcyBpcyBhIF9jaG9pY2VfISkKLSBrbm4gYXBwcm9hY2ggd2l0aCBrID0gMTAsIGFzIGEgc3RhcnRpbmcgcG9pbnQKLSBubyB3ZWlnaHRpbmcKCmBgYHtyfQpjb2xHcmFwaChzZmUsICJrbm4xMCIpIDwtIFNwYXRpYWxGZWF0dXJlRXhwZXJpbWVudDo6ZmluZFNwYXRpYWxOZWlnaGJvcnMoCiAgc2ZlLCAKICB0eXBlID0gImNlbnRyb2lkcyIsIAogIG1ldGhvZCA9ICJrbmVhcm5laWdoIiwgCiAgayA9IDEwCikKYGBgCgpUaGlzIHdpbGwgZ2V0IHN0b3JlZCBpbiBgY29sR3JhcGhgIHNsb3QgaW4gdGhlIGBTRkVgIG9iamVjdC4KCldlIGNhbiB2aXN1YWxpemUgaXQgLSBjaGVjayBvdXQgdGhvc2UgZWRnZXMgYW5kIG5vZGVzLCBpdCdzIGEgZ3JhcGghCgpgYGB7cn0KVm95YWdlcjo6cGxvdENvbEdyYXBoKAogIHNmZSwgCiAgY29sR3JhcGhOYW1lID0gImtubjEwIiwgCiAgY29sR2VvbWV0cnlOYW1lID0gImNlbnRyb2lkcyIsIAogIHNlZ21lbnRfc2l6ZSA9IDAuMSwKICBnZW9tZXRyeV9zaXplID0gMC4zCikgKwogIHRoZW1lX3ZvaWQoKSAjIHdlIGp1c3Qgd2FudCB0aGUgZ3JhcGgKYGBgCgpIZXkgbm90aWNlIGFueXRoaW5nIGludGVyZXN0aW5nIGFib3V0IHRoaXMgaW1hZ2U/Ckl0J3MgYSBtaXJyb3Igb2YgaG93IGBnZ3NwYXZpc2AgcGxvdHRlZCBpdCEKSXMgdGhhdCBhIHByb2JsZW0/IApXZWxsLCBubyAtIGluIHNwYWNlIHdlIGNhbiBtaXJyb3IgYW5kIHJvdGF0ZSBhbmQgc3VjaCwgYnV0IHRoZSBzcGF0aWFsIHJlbGF0aW9uc2hpcHMgYXJlIHByZXNlcnZlZC4KVGhpcyBpcyBhIGJpZyBoaW50IHRoYXQgc3BlY2lmaWMgY29vcmRpbmF0ZXMgbWF5IG5vdCBhbHdheXMgYmUgdGhlIHNhbWUsIGJ1dCB0aGUgZGlzdGFuY2VzIHNob3VsZCBiZS4KCgpJdCBzZWVtcyBwb3RlbnRpYWxseSBjdXRlIHRvIHpvb20gaW4gdG8gcHJvdmUgdG8geW91IHRoYXQgYGsgPSAxMGA6CgpgYGB7cn0KVm95YWdlcjo6cGxvdENvbEdyYXBoKAogIHNmZSwgCiAgY29sR3JhcGhOYW1lID0gImtubjEwIiwgCiAgY29sR2VvbWV0cnlOYW1lID0gImNlbnRyb2lkcyIsIAogIHNlZ21lbnRfc2l6ZSA9IDAuMSwKICBnZW9tZXRyeV9zaXplID0gNQopICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygxMTUwMCwgMTIwMDApKSArIAogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDQwMDAsNDMwMCkpICsKICB0aGVtZV92b2lkKCkKYGBgCgoKQWxyaWdodCwgbm93IHRoYXQgd2UgaGF2ZSBhIGdyYXBoLCBsZXQncyBzZWUgYW4gZXhhbXBsZSBvZiB1c2luZyBpdCBmb3IgZXhwbG9yYXRvcnkgYW5hbHlzaXMuCkEgY29tbW9uIGRlc2NyaXB0aXZlIHN0YXRpc3RpYyB1c2VkIGZvciBsYXR0aWNlIHNwYXRpYWwgZGF0YSBpcyAiTW9yYW4ncyBJIiB3aGljaCBxdWFudGlmaWVzIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uLgpJdCBhc2tzLCBpcyB0aGUgc3BhdGlhbCBkaXN0cmlidXRpb24gb2YgYSB2YXJpYWJsZSBvZiBpbnRlcmVzdCBkaXNwZXJzZWQsIHJhbmRvbSwgb3IgY2x1c3RlcmVkPwpXZSdsbCB1c2UgaXQgdG8gbG9vayBhdCBnZW5lIGV4cHJlc3Npb24gb2Ygc29tZSBvZiBvdXIgbWFya2VyIGdlbmVzIG9mIGludGVyZXN0LgpJbiB0aGlzIGNhc2UsIE1vcmFuJ3MgSSB0ZWxscyB1czogZG8gbmVpZ2hib3Jpbmcgc3BvdHMgKGhpbnQ6IHRoaXMgd2lsbCBkZXBlbmQgb24geW91ciBkZWZpbml0aW9uIG9mIGEgX25laWdoYm9yaG9vZF8hXykgaGF2ZSB2ZXJ5IHNpbWlsYXIsIHZlcnkgZGlzc2ltaWxhciwgb3IgdG90YWxseSB1bnJlbGF0ZWQgZ2VuZSBleHByZXNzaW9uLCB3aGVyZToKCi0gY2xvc2UgdG8gMSB2YWx1ZXM6IE5lYXJieSBzcG90cyB0ZW5kIHRvIGhhdmUgc2ltaWxhciBleHByZXNzaW9uCi0gfjA6IGxvb2tzIHJhbmRvbSBpbiBzcGFjZQotIGNsb3NlIHRvIC0xIHZhbHVlczogTmVhcmJ5IHNwb3RzIHRlbmQgdG8gaGF2ZSBkaWZmZXJlbnQgZXhwcmVzc2lvbiAobGVzcyBjb21tb25seSBvZiBpbnRlcmVzdCBpbiB0aGlzIHNvcnQgb2YgYmlvbG9neSkKCldlJ2xsIGNhbGN1bGF0ZSB0aGlzIHdpdGggYFZveWFnZXJgIG9uIG91ciBgU0ZFYCBvYmplY3QgdXNpbmcgdGhlIGdyYXBoIHdlIGp1c3QgYnVpbHQuCgpgYGB7cn0Kc2ZlIDwtIFZveWFnZXI6OnJ1blVuaXZhcmlhdGUoCiAgc2ZlLCAKICB0eXBlID0gIm1vcmFuIiwgCiAgZmVhdHVyZXMgPSBtYXJrZXJfZ2VuZXMsIAogIGNvbEdyYXBoTmFtZSA9ICJrbm4xMCIKKQpgYGAKClRoaXMgc3RvcmVzIHJlc3VsdHMgaW4gYHJvd0RhdGFgOgoKYGBge3J9CnJvd0RhdGEoc2ZlKVttYXJrZXJfZ2VuZXMsIF0KYGBgCgpUaGlzIG1ha2VzIHNvbWUgc2Vuc2UgZ2l2ZW4gd2hhdCB3ZSB3aGVuIHBsb3R0aW5nIGdlbmUgZXhwcmVzc2lvbjoKCi0gYFNJWDFgIGFuZCBgTVlDTmAgd2VyZSBqdXN0IHNwbG90Y2hlZCBhbGwgYXJvdW5kIHJhbmRvbWx5IGluIG11Y2ggb2YgdGhlIHNsaWRlCi0gYENPTDFBMWAgYW5kIGBUQUdMTmAgIHdlcmUgbW9yZSBjb25jZW50cmF0ZWQgaW4gY2VydGFpbiBhcmVhcwotIGBXVDFgIHdhcyBzb21ld2hlcmUgaW4gdGhlIG1pZGRsZSAtIGdlbmVyYWxseSBleHByZXNzZWQgYnkgbGVzcyB2YXJpYXRpb24gZnJvbSBzcG90LXRvLXNwb3QgaW4gdGhlIHBsb3RzIGVhcmxpZXIsIHBlciB2aWJlcwoKVGhpcyBnbG9iYWwgc3RhdGlzdGljIGdhdmUgdXMgYW4gb3ZlcmFsbCBwaWN0dXJlLCBidXQgd2UgY2FuIGNhbGN1bGF0ZSBhIHZhcmlhbnQgb2YgdGhpcyBzdGF0aXN0aWMgYWxzbyBjYWxsZWQgTG9jYWwgTW9yYW4ncyBJIHdoaWNoIGlzLCB5b3UgZ3Vlc3NlZCBpdCwgYSBsb2NhbCB2ZXJzaW9uLgpSYXRoZXIgdGhhbiBnaXZpbmcgYSBzaW5nbGUgdmFsdWUgZm9yIHRoZSBkYXRhc2V0LCB3ZSdsbCBnZXQgYSBfcGVyLXNwb3RfIHZhbHVlIChvciByYXRoZXIsIHBlciBub2RlIGluIHRoZSBncmFwaCkgdGhhdCB0ZWxscyB1cyAiaG93IG11Y2ggZG9lcyB0aGlzIHNwb3QgYWdyZWUgd2l0aCBpdHMgaW1tZWRpYXRlIG5laWdoYm9yaG9vZCI/CgotIGhpZ2g6IHNwb3QgaXMgc2ltaWxhciB0byBuZWlnaGJvcnMgYW5kIGFsbCBhcmUgaGlnaCBfT1JfIGFsbCBhcmUgbG93Ci0gfjA6IHNwYXRpYWxseSBuZXV0cmFsLCBubyByZWFsIHJlbGF0aW9uc2hpcCB0byBuZWlnaGJvcnMKLSBsb3c6IHNwb3QgaXMgZGlmZmVyZW50IGZyb20gbmVpZ2hib3JzIHdoZXJlIGhpZ2ggc3Vycm91bmRlZCBieSBsb3cgb3IgdmljZSB2ZXJzYQoKTGV0J3MgZ286CgpgYGB7cn0Kc2ZlIDwtIFZveWFnZXI6OnJ1blVuaXZhcmlhdGUoCiAgc2ZlLCAKICB0eXBlID0gImxvY2FsbW9yYW4iLCAKICBmZWF0dXJlcyA9IG1hcmtlcl9nZW5lcywgCiAgY29sR3JhcGhOYW1lID0gImtubjEwIiwgCiAgbmFtZSA9ICJsb2NhbG1vcmFuX2tubjEwIiAjIHNhdmUgaXQgYXMgdGhpcyBuYW1lCikKCiMgc3RvcmVzIGl0IGhlcmUsIG5vdCBzbyBwcmV0dHkuLi4KbG9jYWxSZXN1bHRzKHNmZSkkbG9jYWxtb3Jhbl9rbm4xMApgYGAKCgpgYGB7ciwgZmlnLndpZHRoID0gMTB9CiMgd2UgY2FuIHBsb3Qgd2l0aCB2b3lhZ2VyLCBzaW5jZSBpdCdzIGluIGEgc3BlY2lhbCBzbG90CiMgb3Igd2UnZCBoYXZlIHRvIHB1bGwgaXQgb3V0IHRvIHN0b3JlIGJhY2sgaW4gb3VyIFNQRQpWb3lhZ2VyOjpwbG90TG9jYWxSZXN1bHQoCiAgc2ZlLAogIG5hbWUgPSAibG9jYWxtb3Jhbl9rbm4xMCIsCiAgZmVhdHVyZXMgPSBtYXJrZXJfZ2VuZXMsCiAgY29sR2VvbWV0cnlOYW1lID0gImNlbnRyb2lkcyIsCiAgZGl2ZXJnZW50ID0gVFJVRSwgIyBwYWxldHRlCiAgZGl2ZXJnZV9jZW50ZXIgPSAwICMgcGFsZXR0ZSAKICApIApgYGAKCkZvciBlYXNpZXIgY29tcGFyaXNvbiwgd2UgY2FuIGFsc28gdXNlIHRoZSBzdGFuZGFyZGl6ZWQgKHotc2NvcmUpIHZhbHVlcyBieSBzcGVjaWZ5aW5nIHRoZSBzcGVjaWZpYyBhdHRyaWJ1dGUgKHRoZSBkZWZhdWx0IHdhcyBgIklpImAsIHRoZSBhY3R1YWwgc3RhdCkuCgpgYGB7ciwgZmlnLndpZHRoID0gMTB9ClZveWFnZXI6OnBsb3RMb2NhbFJlc3VsdCgKICBzZmUsCiAgbmFtZSA9ICJsb2NhbG1vcmFuX2tubjEwIiwKICBmZWF0dXJlcyA9IG1hcmtlcl9nZW5lcywKICBhdHRyaWJ1dGUgPSAiWi5JaSIsICMgcGxvdCB0aGUgbm9ybWFsaXplZCBtb3JhbidzIGkKICBjb2xHZW9tZXRyeU5hbWUgPSAiY2VudHJvaWRzIiwKICBkaXZlcmdlbnQgPSBUUlVFLCAKICBkaXZlcmdlX2NlbnRlciA9IDAgCiAgKSAKYGBgCgojIyMgU2NhbGUgbWF0dGVycyEKCldlIGRpZCBhIGdyYXBoIHdpdGggMTAgbmVhcmVzdCBuZWlnaGJvcnMuIApUaGUgbnVtYmVyIG9mIG5laWdoYm9ycyBpcyBnb2luZyB0byBtYXR0ZXIgaGVyZSEKClBpY2sgb25lIGdlbmUgKHByb3ZpZGUgdGhlIGVuc2VtYmwgaWQpIGFuZCBzZWUgaG93IGludGVycHJldGF0aW9uIGRpZmZlcnMgYXQgZGlmZmVyZW50IGBrYCdzLgpUcnkgb3V0IGEgcmVhbGx5IGhpZ2ggKGBrPj01MGApIGFuZCByZWFsbHkgbG93IG51bWJlciAoYGs8PTNgKSBiZWxvdyB0byBjYWxjdWxhdGUgYW5kIHBsb3QgTG9jYWwgTW9yYW4ncyBJIGFnYWluIC0gdGhlIHJlYWwgdmFsdWUsIG5vdCBzY2FsZWQuCldoYXQgZG8geW91IHNlZT8KCmBgYHtyfQpnZW5lIDwtIG1hcmtlcl9nZW5lc1sxXSAjIFdUMSwgZm9yIGV4YW1wbGUKY29sR3JhcGgoc2ZlLCAia25uMTAwIikgPC0gZmluZFNwYXRpYWxOZWlnaGJvcnMoc2ZlLCB0eXBlPSJjZW50cm9pZHMiLCBtZXRob2QgPSAia25lYXJuZWlnaCIsIGs9MTAwKQpjb2xHcmFwaChzZmUsICJrbm4yIikgPC0gZmluZFNwYXRpYWxOZWlnaGJvcnMoc2ZlLCB0eXBlPSJjZW50cm9pZHMiLCBtZXRob2QgPSAia25lYXJuZWlnaCIsIGs9MikKCnNmZSA8LSBWb3lhZ2VyOjpydW5Vbml2YXJpYXRlKAogIHNmZSwgCiAgdHlwZT0ibG9jYWxtb3JhbiIsIAogIGZlYXR1cmVzID0gZ2VuZSwgCiAgY29sR3JhcGhOYW1lPSJrbm4xMDAiLCAKICBuYW1lID0gImxvY2FsbW9yYW5fa25uMTAwIiAKKQpzZmUgPC0gVm95YWdlcjo6cnVuVW5pdmFyaWF0ZSgKICBzZmUsIAogIHR5cGU9ImxvY2FsbW9yYW4iLCAKICBmZWF0dXJlcyA9IGdlbmUsIAogIGNvbEdyYXBoTmFtZT0ia25uMiIsIAogIG5hbWUgPSAibG9jYWxtb3Jhbl9rbm4yIiAKKQoKcDEgPC0gVm95YWdlcjo6cGxvdExvY2FsUmVzdWx0KAogIHNmZSwKICBuYW1lID0gImxvY2FsbW9yYW5fa25uMTAwIiwKICBmZWF0dXJlcyA9IGdlbmUsCiAgY29sR2VvbWV0cnlOYW1lID0gImNlbnRyb2lkcyIsCiAgZGl2ZXJnZW50ID0gVFJVRSwKICBkaXZlcmdlX2NlbnRlciA9IDAKKSAKCgpwMiA8LSBWb3lhZ2VyOjpwbG90TG9jYWxSZXN1bHQoCiAgc2ZlLAogIG5hbWUgPSAibG9jYWxtb3Jhbl9rbm4yIiwKICBmZWF0dXJlcyA9IGdlbmUsCiAgY29sR2VvbWV0cnlOYW1lID0gImNlbnRyb2lkcyIsCiAgZGl2ZXJnZW50ID0gVFJVRSwKICBkaXZlcmdlX2NlbnRlciA9IDAKKSAKCnAxICsgcDIKYGBgCgpXaGVuIGBrYCBpcyB2ZXJ5IGhpZ2gsIHRoZXJlJ3MgYSBsb3Qgb2YgbmVpZ2hib3JzIGJlaW5nIGNvbnNpZGVyLgpFeHByZXNzaW9uIHdpbGwgaGF2ZSB0byBiZSBzaW1pbGFyIGNyb3NzIHRoZW0gYWxsIHRvIGdldCBhIGhpZ2ggdmFsdWUgZm9yIHRoaXMgc3RhdC4KQnV0LCB3aGVuIGBrYCBpcyB2ZXJ5IGxvdywgdGhlcmUncyB2ZXJ5IGZldyBuZWlnaGJvcnMhCk11Y2ggZmluZXItZ3JhaW5lZCBwaWN0dXJlIG9mIHNpbWlsYXJpdHkgYmV0d2VlbiBzcG90cy4KCgojIyBTZXNzaW9uIEluZm8KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAo=